Next.js App on IIS: 404 Error on Subpage Navigation Without .html Extension

Issue with Next.js on IIS: Subpage Navigation Fails Without .html

The error i see on the browser: HTTP Error 404.0 – Not Found
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.

I have a Next.js app running on an IIS server. The app’s base URL (e.g., site.com) works fine, but navigating to subpages (e.g., site.com/subpage) fails unless I append .html (e.g., site.com/subpage.html). Additionally, navigation works within the app until I refresh the page or enter the URL manually.
Navigating the app without touching the browser url or refresh button works fine, it navigates me to the correct urls and even updates the url to the navigated page. so if i go from site.com to the about page, it updates the url to site.com/about. But the moment i press the refresh button or when i place my cursor inside of the url and press enter, i get the 404 error.

important to know is that i use the app router nextjs provides so i need the app to be getting its routing trough the nextjs server which is running on NodeJs. The no managed code is also toggled in the app pool on iis.

Environment:

  • Next.js: Version 14.1.3
  • IIS: Version 23H2
  • iisnode: Installed via Azure iisnode
  • url rewrite: Installed via iis.net (i didn’t add any configurations as i was told by google searches the webconfig handles this)

enter image description here

Project Tree

├───app    
    ├───about (site.com/about)
    ├───api
    │   └───page.tsx
    ├───config (these are actual screens that should route to site.com/config/*)
    │   ├───activity
    │   │   └───page.tsx
    │   ├───components
    │   │   └───page.tsx
    │   ├───issuetype
    │   │   └───page.tsx
    │   ├───scenario
    │   │   └───page.tsx
    │   └───subprocess
    │       └───page.tsx
    ├───dashboard (site.com/dashboard)
    │   └───page.tsx
    ├───functions
    ├───interfaces
    └───services
├───.env.local
├───next.config.ts
├───package.json
├───server.js
├───tsconfig.json
├───web.config

The out folder looks like this

C:.
├──config
├──_next
│  ├───nBWbtfKUl7qhX9oaAecjF
│  └───static
│      ├───chunks
│      │   ├───app
│      │   │   ├───about
│      │   │   ├───config
│      │   │   │   ├───activity
│      │   │   │   ├───issuetype
│      │   │   │   ├───scenario
│      │   │   │   └───subprocess
│      │   │   └───dashboard
│      │   └───pages
│      ├───css
│      ├───media
│      └───nBWbtfKUl7qhX9oaAecjF
├──404.html
├──about.html
├──about.txt
├──dashboard.html
├──dashboard.txt
├──index.html
├──index.txt
├──next.html
└──vercel.html

Current web.config in IDE:

<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="NextJs Routes Handler" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
            <!-- Exclude specific paths from rewriting -->
            <add input="{REQUEST_URI}" pattern="^/(api|_next/static|static)/" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>

    <iisnode node_env="test" nodeProcessCommandLine=""C:Program Filesnodejsnode.exe"" interceptor=""%programfiles%iisnodeinterceptor.js"" />

  </system.webServer>
  <location path="" overrideMode="Deny">
    <system.webServer>
      <handlers>
        <add name="iisnode" path="hello.js" verb="*" modules="iisnode" />
      </handlers>
    </system.webServer>
  </location>
</configuration>

This gets formatted to web.config in IDE:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <security>
            <authorization>
                <remove users="*" roles="" verbs="" />
                <add accessType="Allow" />
                <add accessType="Allow" roles="GG-DEP-IT" />
            </authorization>
        </security>
    </system.webServer>
</configuration>

web.config file

<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="NextJs Routes Handler" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
            <!-- Exclude specific paths from rewriting -->
            <add input="{REQUEST_URI}" pattern="^/(api|_next/static|static)/" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>

    <iisnode node_env="test" nodeProcessCommandLine=""C:Program Filesnodejsnode.exe"" interceptor=""%programfiles%iisnodeinterceptor.js"" />

  </system.webServer>
  <location path="" overrideMode="Deny">
    <system.webServer>
      <handlers>
        <add name="iisnode" path="server.js" verb="*" modules="iisnode" />
      </handlers>
    </system.webServer>
  </location>
</configuration>

server.js file

const {createServer} = require('http');
const {parse} = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const port = process.env.PORT || 3000;
const app = next({dev});
const handle = app.getRequestHandler();

app.prepare().then(() => {
    createServer((req, res) => {
        // Be sure to pass `true` as the second argument to `url.parse`.
        // This tells it to parse the query portion of the URL.
        const parsedUrl = parse(req.url, true);
        const {pathname, query} = parsedUrl;

        handle(req, res, parsedUrl);
    }).listen(port, (err) => {
        if (err) throw err;
        console.log(`> Ready on http://localhost:${port}`);
    });
});

next.config.ts file

/** @type {import('next').NextConfig} */
const nextConfig = {
    output: 'export',
    experimental: {},
    reactStrictMode: true,
    swcMinify: true,
};

export default nextConfig;

package.json file

{
  "name": "reasoncodes",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "next export",
    "lint": "next lint",
    "test": "vitest --watch --ui --coverage.enabled=true"
  },
  "dependencies": {
    "@next/env": "^14.2.3",
    "@testing-library/react": "^14.2.2",
    "@vitejs/plugin-react": "^4.2.1",
    "axios": "^1.6.7",
    "jsdom": "^24.0.0",
    "next": "14.1.3",
    "primeflex": "^3.3.1",
    "primeicons": "^6.0.1",
    "primereact": "^10.6.5",
    "react": "^18",
    "react-dom": "^18",
    "react-hook-form": "^7.51.0",
    "vite-tsconfig-paths": "^4.3.2"
  },
  "devDependencies": {
    "@hookform/devtools": "^4.3.1",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@vitest/coverage-v8": "^1.4.0",
    "@vitest/ui": "^1.4.0",
    "eslint": "^8",
    "eslint-config-next": "14.1.3",
    "typescript": "^5",
    "vitest": "^1.4.0"
  }
}

enter image description here
enter image description here
enter image description here
enter image description here

I tried following lots of guides but none seem to help me.
im out of ideas at this point and this is not worthy of shipping out to a customer.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật