afterbuild/ops
ERR-318/Next.js · Routing
ERR-318
404 This page could not be found — but only on production for dynamic routes

appears when:When a dynamic route has no generateStaticParams and dynamic = 'error' is set, or next.config uses output: 'export' which requires every param to be pre-rendered

404 on dynamic route after deploy

Next.js lets you choose static or dynamic rendering per route. Pick wrong and production returns 404 for URLs that work fine in dev.

Last updated 17 April 2026 · 7 min read · By Hyder Shah
Direct answer
Decide whether the IDs are known at build time or at request time. If known, export async function generateStaticParams() returning the IDs. If unknown, export const dynamic = "force-dynamic". If your next.config has output: "export", you must pre-render every valid ID or drop dynamic routes.

Quick fix for 404 on dynamic route after deploy

app/users/[id]/page.tsx
typescript
01// app/users/[id]/page.tsx02// Option A: dynamic rendering — IDs unknown at build time03export const dynamic = "force-dynamic";04 05export default async function UserPage({06  params,07}: {08  params: Promise<{ id: string }>;09}) {10  const { id } = await params;11  const user = await fetchUser(id);12  if (!user) notFound();13  return <UserCard user={user} />;14}15 16// Option B: static generation for a known set of IDs17export async function generateStaticParams() {18  const users = await listUsers();19  return users.map((u) => ({ id: u.id }));20}21 22// Option C: allow any ID but pre-render the common ones23export const dynamicParams = true; // default; render unknown IDs on request
Three options — pick one based on whether IDs are known at build time or only at request time

Deeper fixes when the quick fix fails

01 · Hybrid: pre-render top IDs, render rest on demand

app/products/[slug]/page.tsx
typescript
01// app/products/[slug]/page.tsx02export const dynamicParams = true; // default — allow unknown slugs at request time03 04export async function generateStaticParams() {05  // Only pre-render the 100 most-viewed products at build06  const top = await db.products.findMany({07    where: { featured: true },08    take: 100,09    select: { slug: true },10  });11  return top.map((p) => ({ slug: p.slug }));12}13 14export default async function ProductPage({15  params,16}: {17  params: Promise<{ slug: string }>;18}) {19  const { slug } = await params;20  const product = await db.products.findUnique({ where: { slug } });21  if (!product) notFound();22  return <ProductDetail product={product} />;23}
Best for e-commerce: top sellers are fast (static), long-tail still works (dynamic)

02 · Route-level static config for ISR

app/blog/[slug]/page.tsx
typescript
01// app/blog/[slug]/page.tsx — Incremental Static Regeneration02export const revalidate = 3600; // revalidate every hour03 04export async function generateStaticParams() {05  const posts = await listPosts();06  return posts.map((p) => ({ slug: p.slug }));07}08 09export default async function BlogPost({10  params,11}: {12  params: Promise<{ slug: string }>;13}) {14  const { slug } = await params;15  const post = await getPost(slug);16  if (!post) notFound();17  return <article>{post.content}</article>;18}
Static for perf, periodic revalidation for freshness — ideal for content sites

03 · Playwright test: every real route returns 200

tests/routes.spec.ts
typescript
01// tests/routes.spec.ts — regression test for route 404s02import { test, expect } from "@playwright/test";03 04const SAMPLE_IDS = ["id-1", "id-2", "id-3"]; // real IDs from prod05 06test.describe("dynamic routes", () => {07  for (const id of SAMPLE_IDS) {08    test(`GET /users/${id} returns 200`, async ({ request }) => {09      const res = await request.get(`/users/${id}`);10      expect(res.status()).toBe(200);11    });12  }13 14  test("unknown user renders notFound()", async ({ request }) => {15    const res = await request.get("/users/definitely-not-real");16    expect(res.status()).toBe(404);17    expect(await res.text()).toContain("not found");18  });19});
CI gate — any dynamic route that stops rendering in prod fails the test

Why AI-built apps hit 404 on dynamic route after deploy

The Next.js App Router decides how to render each route at build time. For a dynamic route like app/users/[id]/page.tsx, Next looks at three signals: the generateStaticParams export, the dynamic export, and the output setting in next.config. Based on these, it chooses one of four behaviors: pre-render a fixed set of IDs at build, pre-render that set plus render unknowns on request, render every request fresh, or throw if any dynamic API is touched. The wrong combination causes production 404s that dev never sees.

The most common failure mode with AI-generated code is missing generateStaticParams combined with an implicit static render. The scaffold creates the dynamic route file with a page component but no export that tells Next how to generate params. On dev the route works because Next renders on demand — dev never pre-renders anything. On build, Next discovers the dynamic route, finds no generateStaticParams, defaults to dynamicParams mode, but then the deploy target (static export) cannot serve dynamic routes at all. Every request 404s.

The second failure mode is a generateStaticParams that returns an empty array. That happens when the build-time data source is not available — for example, the function queries a database that is not configured in the build env, so the query throws but is silently caught. Next then has zero params to pre-render and no fallback, so every URL 404s. Check your build log for warnings about empty param arrays.

The third failure is output: "export". Static export was popular when deploying to S3 or GitHub Pages. It produces pure HTML and JS with no server. Under static export, dynamic routes require every valid ID to be known at build time — there is no server to handle unknown IDs at request time. If your generateStaticParams returns only 10 IDs but your app has 10,000 users, only those 10 URLs will work. The other 9,990 will 404. The fix is to drop static export or switch to SSR mode on your host.

404 on dynamic route after deploy by AI builder

How often each AI builder ships this error and the pattern that produces it.

AI builder × 404 on dynamic route after deploy
BuilderFrequencyPattern
LovableEvery dynamic route scaffoldOmits generateStaticParams, assumes dev behavior applies to prod
Bolt.newCommonLeaves output: 'export' from an earlier static scaffold after adding dynamic routes
CursorCommonWrites generateStaticParams with a db call that throws silently — empty array result
Base44SometimesUses dynamic = 'error' as default because of a copied example
Replit AgentRareCreates duplicate route files at [id] and [...slug] — conflict causes 404

Related errors we fix

Stop 404 on dynamic route after deploy recurring in AI-built apps

Still stuck with 404 on dynamic route after deploy?

Emergency triage · $299 · 48h turnaround
We restore service and write the root-cause report.
start the triage →

404 on dynamic route after deploy questions

Why does a dynamic route like /users/[id] 404 only in production?+
In dev, every dynamic route is rendered on demand — Next.js never decides whether to pre-render. In production with the App Router default, Next.js tries to determine if each route should be statically rendered at build time or dynamically at request time. If your next.config has output: 'export' or the page exports generateStaticParams returning an empty array, Next builds zero static pages and also has no dynamic fallback, so every request 404s. Fix by either adding generateStaticParams with real IDs, or marking the route dynamic explicitly.
What is generateStaticParams and when do I need it?+
generateStaticParams is an exported async function in a dynamic route file that returns an array of param combinations to pre-render at build time. You need it when you want static generation for a known set of slugs — blog posts, product pages, a small user directory. For unknown or user-generated content at runtime, you do not want generateStaticParams — instead set export const dynamic = 'force-dynamic' or dynamicParams = true so Next renders on request.
What is the difference between dynamic = 'error' and dynamic = 'force-dynamic'?+
dynamic = 'error' forces static generation and throws if any request-time API is used (cookies, headers, searchParams). dynamic = 'force-dynamic' forces rendering on every request — no caching, no static optimization. dynamic = 'auto' is the default and lets Next decide. If your page 404s in prod, 'force-dynamic' is the escape hatch: it guarantees the route always renders for any param. Use it when the full set of valid IDs is not known at build time.
Why does removing output: 'export' break my deploy?+
Static export produces a dist/ directory of pure HTML and JS that can serve from any static host — S3, Netlify, Vercel static, GitHub Pages. Removing it means the deploy now needs a server runtime. On Vercel that is automatic. On other hosts you need to configure Node or Edge support. If your deploy target requires static output, keep the export flag but drop all dynamic routes or pre-render every ID with generateStaticParams.
How much does an Afterbuild Labs routing audit cost?+
For a Next.js app with 20-50 routes, a full audit including static/dynamic strategy, generateStaticParams correctness, and a routing regression test takes about 2 hours. Our Deployment and Launch service is fixed-fee and includes the audit, a test harness that asserts every route returns 200, and a deploy checklist specific to your hosting target (Vercel, Cloudflare, AWS).
Next step

Ship the fix. Keep the fix.

Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.

About the author

Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.

Sources