next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Next 16.0.0/16.0.1-canary.2: next/link Prefetch Causes 404 for RSC Payloads in Static Export

Open Aaakul opened this issue 1 month ago • 10 comments

Link to the code that reproduces this issue

https://github.com/Aaakul/next16-next-link-rsc-issue

To Reproduce

  1. Clone the reproduction repository
  2. Run cd next16
  3. Run npm install
  4. Run npm run build (output: 'export' is already set in next.config.ts)
  5. Run npx serve out
  6. Visit /next16 in a browser (e.g., http://localhost:3000/next16), open developer tools and check the console for 404 errors. Click the link to navigate to /next16/page, refresh and check if similar errors appears.
  7. Run cd ..
  8. Run cd next15
  9. Repeat 3~6

Current vs. Expected behavior

Current Behavior (Next.js 16.0.0/16.0.1-canary.2/16.0.1 + output: 'export'):

When visiting static exported page has next/link with default prefetching or is explicitly set to true / "auto" / null, the page attempts to fetch the target page's RSC Payload from incorrect path, resulting in a 404 Not Found error (e.g., '.../page/__next.page.PAGE.txt?_rsc=...'). Image

Expected Behavior (Next.js 15.5.6 + output: 'export'):

Invalid RSC requests are not expected in statically exported pages

Provide environment information

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 10 Home
  Available memory (MB): 32578
  Available CPU cores: 16
Binaries:
  Node: 20.19.5
  npm: 10.8.2
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 16.0.1
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: export

Which area(s) are affected? (Select all that apply)

Linking and Navigating, Output

Which stage(s) are affected? (Select all that apply)

next build (local), Other (Deployed)

Additional context

  • Reproducible Versions: Next.js 16.0.0, 16.0.1-canary.2 and 16.0.1
  • This issue does not exist in Next.js 15.5.6
  • This issue is reproducible locally using npx serve out and is also observed on static hosting platforms like GitHub Pages: https://aaakul.github.io/next16/ and Cloudflare Pages: https://next16-next-link-rsc-issue.pages.dev/
  • There are differences in stability of reproducible across browsers

Comparing my demo's output .txt files:

  • Next 15.5.6: index.txt page.txt
  • Next 16.0.1-canary.2:
│  index.txt
│  page.txt
│  _not-found.txt
│  __next._full.txt
│  __next._index.txt
│  __next._tree.txt
│  __next.__PAGE__.txt
│
|─page
│  │  __next.page.txt
│  │  __next._full.txt
│  │  __next._index.txt
│  │  __next._tree.txt
│  │
│  └─__next.page
│          __PAGE__.txt
│
└─_not-found
    │  __next._full.txt
    │  __next._index.txt
    │  __next._not-found.txt
    │  __next._tree.txt
    └─__next._not-found
            __PAGE__.txt

Aaakul avatar Oct 26 '25 15:10 Aaakul

Same happens for non-static exports as well, though only when deploying to Vercel for some reason: https://github.com/vercel/next.js/issues/85349

akshatmittal avatar Oct 26 '25 17:10 akshatmittal

Hi @Aaakul --

I took a look at your reproduction, and both apps are 404ing when I run them locally with your instructions. I didn't see anything specific to Next 16 in here, but it looks like you might have stumbled across a bug with basePath when combined with output: export (see: https://github.com/vercel/next.js/issues/67817)

Your deployed demo also seems to be working as expected.

Can you clarify if this is just an issue with basePath or something novel?

ztanner avatar Oct 29 '25 16:10 ztanner

@ztanner For this demo, make sure you're accessing the correct paths (/next16) after building it, then running serve. This is not related to basePath specifically, I have an app locally which does the same without basePath set. Also see the issue I linked above, does the same thing.

I dug deeper into it and found out that Next is resolving incorrect paths when querying for RSC data, here's an example from my local repo below:

(top is what Next requests, bottom is what Next should request)

/hello/sq-1213/__next.!KGV2bSk.hello.$d$slugger.__PAGE__.txt?_rsc=69ns0 /hello/sq-1213/__next.!KGV2bSk/hello/$d$slugger/__PAGE__.txt?_rsc=69ns0

This is for a page with statically generated dynamic params /hello/[slugger]

Same happens for the linked demo in this issue, easily reproducible by opening the linked demo deployment. (You'll see a 404 in the network panel for the path listed below)

(top is what Next requests, bottom is what Next should request)

/page/__next.page.__PAGE__.txt?_rsc=g7psd (link - 404s) /page/__next.page/__PAGE__.txt?_rsc=g7psd (link)

akshatmittal avatar Oct 30 '25 12:10 akshatmittal

Hi @Aaakul --

I took a look at your reproduction, and both apps are 404ing when I run them locally with your instructions. I didn't see anything specific to Next 16 in here, but it looks like you might have stumbled across a bug with basePath when combined with output: export (see: #67817)

Your deployed demo also seems to be working as expected.

Can you clarify if this is just an issue with basePath or something novel?

Hi ztanner,

Thank you very much for taking the time to test!

I think the issue I reported is not related to basePath. I initially created a version without basePath. Adding basePath is for deploying the test demo on GitHub Pages. I have deployed a version using Next.js 16.0.1 on Cloudflare Pages that has no basePath configured: Cloudflare Pages URL with Next 16.0.1 and NO basePath

When visiting this link with Latest Chrome, Edge or Firefox, the 404 error is still consistently reproducible.

I also noticed some differences in stability across browsers on the previously provided GitHub Pages demo (Next 16.0.0 with basePath) and Cloudflare Pages demo (Next 16.0.1 without basePath):

  • Latest Chrome: The 404 error is consistently reproduced both on GitHub Pages and Cloudflare Pages.
  • Latest Firefox: The 404 error is consistently reproduced on Cloudflare Pages, not ocurr on GitHub Pages.
  • Latest Edge: The 404 error is consistently reproduced on Cloudflare Pages. For GitHub Pages demo, the error is sometimes not ocurr, or it flashes quickly. By enabling "Preserve Log", I confirmed that the fleeting error is from the exact same function within the same JavaScript chunk that shows in Chrome/Firefox. This function appears to be responsible for handling the RSC payload request.

Do you need me to provide any additional testing or information?

Aaakul avatar Oct 30 '25 14:10 Aaakul

@ztanner For this demo, make sure you're accessing the correct paths (/next16) after building it, then running serve. This is not related to basePath specifically, I have an app locally which does the same without basePath set. Also see the issue I linked above, does the same thing.

I dug deeper into it and found out that Next is resolving incorrect paths when querying for RSC data, here's an example from my local repo below:

(top is what Next requests, bottom is what Next should request)

/hello/sq-1213/__next.!KGV2bSk.hello.$d$slugger.__PAGE__.txt?_rsc=69ns0 /hello/sq-1213/__next.!KGV2bSk/hello/$d$slugger/__PAGE__.txt?_rsc=69ns0

This is for a page with statically generated dynamic params /hello/[slugger]

Same happens for the linked demo in this issue, easily reproducible by opening the linked demo deployment. (You'll see a 404 in the network panel for the path listed below)

(top is what Next requests, bottom is what Next should request)

/page/__next.page.__PAGE__.txt?_rsc=g7psd (link - 404s) /page/__next.page/__PAGE__.txt?_rsc=g7psd (link)

Hi akshatmittal, Thanks so much for digging into this.

Based on my testing, the error not occur may be related to the browser platform. You can find the details in my reply to ztanner.

Aaakul avatar Oct 30 '25 15:10 Aaakul

I also noticed some differences in stability across browsers

@Aaakul Very interesting that you are seeing differences based on browsers, I'm not seeing that for my local app. Tested in Chrome, Edge, Firefox and Comet. I'm able to reproduce this behaviour in every browser.

The exact same paths I mentioned in my previous reply from the demo also apply to the Cloudflare deployment that @Aaakul provided, so does seem like it's the path resolution issue for RSC payloads I've described.

@ztanner Also confirmed this exists in the latest canary.

akshatmittal avatar Oct 30 '25 15:10 akshatmittal

Having the same issues for the non-static pages

@Aaakul, @akshatmittal did you find any workaround??

Heet-Bhalodiya avatar Nov 10 '25 13:11 Heet-Bhalodiya

Having the same issues for the non-static pages

@Aaakul, @akshatmittal did you find any workaround??

Currently, I'm using next/link with prefetch={false}. In App Router, this will not prefetch any data, even on hover. Haven't tested in non-static export yet.

      <Link
        prefetch={false}
        {...rest}
      />

Aaakul avatar Nov 11 '25 06:11 Aaakul

Just wanted to chime to say that this doesn't appear to be confined to static exports, but occurs for both SSR'd and static pages. I'm not using a basePath nor an output in my next config (see below). Further digging points to the prefetch request header not being handled properly server-side...

Next Config
import type { NextConfig } from "next";

const inStaging = process.env.NEXT_PUBLIC_IN_DEVELOPMENT === "true";


const nextConfig: NextConfig = {
    experimental: {
        turbopackFileSystemCacheForDev: true
    },
    compiler: {
        reactRemoveProperties:
            process.env.NODE_ENV === "production" && !inStaging ? { properties: ["^data-testid$"] } : false
    },
    allowedDevOrigins: ["127.0.0.1", "localhost"],
    devIndicators: false,
    trailingSlash: true,
    images: {
        minimumCacheTTL: 2678400,
        qualities: [60, 75, 100],
        remotePatterns: [
            {
                protocol: "http",
                hostname: "127.0.0.1",
                port: "54321",
                pathname: "/**"
            },
            // Production domains
            {
                protocol: "https",
                hostname: "example.com",
                pathname: "/**"
            },
            {
                protocol: "https",
                hostname: "example.dev",
                pathname: "/**"
            },
            // Vercel preview URLs
            {
                protocol: "https",
                hostname: "*.vercel.app",
                pathname: "/**"
            },
            {
                protocol: "https",
                hostname: "i.imgur.com",
                pathname: "/**"
            },
            {
                protocol: "https",
                hostname: "lh3.googleusercontent.com",
                pathname: "/a/**"
            },
            {
                protocol: "https",
                hostname: "media.licdn.com",
                pathname: "/dms/image/**"
            }
        ]
    },
    async headers() {
        const securityHeaders = [
            {
                key: "X-DNS-Prefetch-Control",
                value: "on"
            },
            {
                key: "X-Frame-Options",
                value: "SAMEORIGIN"
            },
            {
                key: "X-XSS-Protection",
                value: "1; mode=block"
            },
            {
                key: "Referrer-Policy",
                value: "origin-when-cross-origin"
            },
            {
                key: "Strict-Transport-Security",
                value: "max-age=63072000; includeSubDomains; preload"
            },
            {
                key: "X-Content-Type-Options",
                value: "nosniff"
            }
        ];

        if (inStaging) {
            securityHeaders.push({
                key: "X-Robots-Tag",
                value: "noindex, nofollow"
            });
        }

        return [
            {
                source: "/(.*)",
                headers: securityHeaders
            }
        ];
    }
};

export default nextConfig;

For this example, I have a /sign-in/ page (dynamic) that has a prefetch link to /sign-up/?_rsc=7dz3b (dynamic).

Request to remote URL hosted on Vercel without headers:

curl -IL --max-redirs 1 'https://www.example.com/sign-up/?_rsc=7dz3b'

HTTP/2 200
age: 0
cache-control: private, no-cache, no-store, max-age=0, must-revalidate
content-type: text/html; charset=utf-8
date: Wed, 12 Nov 2025 21:11:03 GMT
link: </_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"
referrer-policy: origin-when-cross-origin
server: Vercel
strict-transport-security: max-age=63072000; includeSubDomains; preload
vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch
x-content-type-options: nosniff
x-current-path: /sign-up/
x-dns-prefetch-control: on
x-frame-options: SAMEORIGIN
x-matched-path: /sign-up
x-powered-by: Next.js
x-vercel-cache: MISS
x-vercel-id: pdx1::sfo1::jfjkf-1762981862970-894886927fd1
x-xss-protection: 1; mode=block

Request to remote URL hosted on Vercel with headers:

 curl -IL --max-redirs 1 'https://www.example.com/sign-up/?_rsc=7dz3b'\
  -H 'accept: */*' \
  -H 'accept-language: en-US,en;q=0.8' \
  -H 'next-router-prefetch: 1' \
  -H 'next-router-segment-prefetch: /_tree' \
  -H 'next-url: /sign-in' \
  -H 'priority: i' \
  -H 'referer: https://www.example.com/sign-in/' \
  -H 'rsc: 1' \
  -H 'sec-ch-ua: "Chromium";v="142", "Brave";v="142", "Not_A Brand";v="99"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: same-origin' \
  -H 'sec-gpc: 1' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
  -H 'x-deployment-id: dpl_H6W1h1TT5ymtymWfaTwP8imidC43'

HTTP/2 404
accept-ranges: bytes
access-control-allow-origin: *
age: 13455
cache-control: public, max-age=0, must-revalidate
content-disposition: inline; filename="404"
content-type: text/html; charset=utf-8
date: Wed, 12 Nov 2025 21:14:18 GMT
etag: "48af11d0fb7ae9a3c3a9255880de0319"
last-modified: Wed, 12 Nov 2025 17:30:03 GMT
referrer-policy: origin-when-cross-origin
server: Vercel
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-content-type-options: nosniff
x-current-path: /sign-up/
x-dns-prefetch-control: on
x-frame-options: SAMEORIGIN
x-matched-path: /404
x-vercel-cache: HIT
x-vercel-id: pdx1::6cgst-1762982058905-f56ab7a0c2a7
x-xss-protection: 1; mode=block
content-length: 85710

Request to remote URL hosted on Vercel removing next-router-prefetch: 1 or next-router-segment-prefetch: /_tree header:

 curl -IL --max-redirs 1 'https://www.example.com/sign-up/?_rsc=7dz3b'\
  -H 'accept: */*' \
  -H 'accept-language: en-US,en;q=0.8' \
  -H 'next-router-segment-prefetch: /_tree' \
  -H 'next-url: /sign-in' \
  -H 'priority: i' \
  -H 'referer: https://www.example.com/sign-in/' \
  -H 'rsc: 1' \
  -H 'sec-ch-ua: "Chromium";v="142", "Brave";v="142", "Not_A Brand";v="99"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: same-origin' \
  -H 'sec-gpc: 1' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
  -H 'x-deployment-id: dpl_H6W1h1TT5ymtymWfaTwP8imidC43'


HTTP/2 200
age: 0
cache-control: private, no-cache, no-store, max-age=0, must-revalidate
content-type: text/x-component
date: Wed, 12 Nov 2025 21:16:15 GMT
referrer-policy: origin-when-cross-origin
server: Vercel
strict-transport-security: max-age=63072000; includeSubDomains; preload
vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch
x-content-type-options: nosniff
x-current-path: /sign-up/
x-dns-prefetch-control: on
x-frame-options: SAMEORIGIN
x-matched-path: /sign-up.rsc
x-vercel-cache: MISS
x-vercel-id: pdx1::sfo1::z6x6x-1762982174954-c25c4bb476a6
x-xss-protection: 1; mode=block

mattcarlotta avatar Nov 12 '25 20:11 mattcarlotta

I don't know if this is related..... The internal file copy function only using / as file path separator. so that, incorrect file copy in Windows build environment.

Linux(WSL) Windows

Also, always fail (404). that's because, incorrect file copy and not exists next/link prefetch link.

opend PR: #86948

takusan23 avatar Dec 09 '25 12:12 takusan23