vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.5k stars 27.04k forks source link

Calling notFound() in ISR generates static files in app router. #73101

Open peterlidee opened 1 week ago

peterlidee commented 1 week ago

Link to the code that reproduces this issue

https://github.com/peterlidee/isr-not-found-bug

To Reproduce

  1. Run next build
  2. Check build folder: .next/app/post/a.html and .next/pages/post2/a.html exist (SSG)
  3. Run next start
  4. Visit /post/a (app router) and /post2/a (pages router). Pages appear as expected.
  5. Visit /post/b (app router) and /post2/b (pages router). Pages appear as expected. (ISR works confirmed)
  6. Visit /post/c (app router) and /post2/c (pages router). 404 as expected. ({ notfound: true } and notFound() work)
  7. Check build pages folder: .next/pages/post2/ now contains 2 static html files: a.html and b.html. No c.html because we returned { notfound: true } from getStaticProps. This is as expected.
  8. Check build app folder: .next/app/post/ now contains 3 static html files: a.html and b.html as expected but also c.html.
  9. c.html should not have been generated because we called notFound().

Current vs. Expected behavior

Following the steps from the previous section, I expected c.html to not exist in the app router build folder but it did exist.

I setup an example running ISR in the app router (https://github.com/peterlidee/isr-not-found-bug/blob/main/src/app/post/[slug]/page.tsx). I use generateStaticParams to generate one page: post/a:

export function generateStaticParams() {
  return [{ slug: 'a' }];
}

In the actual functional component, I only allow slugs "a" or "b":

export default async function page({ params }: Props) {
  const slug = (await params).slug;
  const validParams = ['a', 'b'];
  if (!validParams.includes(slug)) {
    notFound();
  }
  return <div>post slug: {slug}</div>;
}

Slug "a" has been prerendered at build time. When visiting /post/b, next will do ISR and generate static html in the app build folder: .next/app/post/b.html. This confirms that ISR works.

The problem lies with slug "c" (or anything other then "a" or "b"). When the slug is "c" notFound() gets called and we get an error 404 as expected. However, when visiting the build folder we see that a static c.html was generated: .next/app/post/c.html.

This is unexpected behaviour: we just told next this page doesn't exist so why generate a static file for this? I copied the functionality of this page in the pages router (https://github.com/peterlidee/isr-not-found-bug/blob/main/src/pages/post2/[slug].tsx) (/post2) using getStaticPaths and getStaticProps and in the pages router c.html does not get generated in the build folder.

I'm not 100% this is a bug because the app router behaviour is similar in Next 13, 14, 15 and canary.

Provide environment information

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 11 Education
  Available memory (MB): 32617
  Available CPU cores: 16
Binaries:
  Node: 20.17.0
  npm: 10.8.2
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 15.0.3 // Latest available version is detected (15.0.3).
  eslint-config-next: 15.0.3
  react: 19.0.0-rc-66855b96-20241106
  react-dom: 19.0.0-rc-66855b96-20241106
  typescript: 5.6.3
Next.js Config:
  output: N/A

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

Not sure

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

next build (local), next start (local)

Additional context

No response

astrodomas commented 3 days ago

https://github.com/vercel/next.js/issues/72456 Could be related