How to Fix: The static indicator should not show for dynamic routes e.g. [slug]/page.tsx
The Static Indicator is misleading for dynamic routes like [slug]/page.tsx because it reflects the current render classification, not the fact that the route segment itself is parameterized.
In practice, that means a route can be dynamic by pathname while still being statically generated at build time. If the indicator labels that page as static, developers can wrongly assume the route is not dynamic at all. This issue typically appears in the App Router when a page uses a dynamic segment such as [slug] but does not opt into runtime rendering.
Table of Contents
Understanding the Root Cause
The confusion comes from two different Next.js concepts being displayed as if they were the same:
- Dynamic route segments: File-system routing such as app/[slug]/page.tsx means the URL contains a variable parameter.
- Rendering strategy: A page can be static, dynamic, or use incremental revalidation depending on data fetching and route configuration.
A page inside [slug]/page.tsx is still a dynamic route because its pathname is parameterized. However, if it uses generateStaticParams(), cached fetches, or no runtime-only APIs, Next.js can pre-render it as static output. The current indicator is therefore describing the render mode, while developers read it as describing the route type.
This is why the indicator appears incorrect: it says static, but the developer expects the UI to communicate that the route itself is dynamic. The bug is not necessarily in route generation, but in how the development overlay labels the page.
Technically, this behavior is tied to how the App Router analyzes:
- segment params from the file name
- build-time prerenderability
- dynamic APIs such as headers, cookies, or uncached data fetching
If no dynamic server signals are detected, the page can be classified as static even when the segment name contains [slug].
Step-by-Step Solution
If you are fixing this issue in framework code, the goal is to make the indicator distinguish between route parameterization and render classification. A dynamic segment should not be shown as simply static without context.
- Identify how the indicator state is computed
Find the code path responsible for the dev overlay or route indicator label. The current logic is usually based on whether the page was statically prerendered or forced dynamic.
- Detect dynamic segments from the route definition
The route should be checked for parameterized segments such as [slug], […slug], or [[…slug]].
function isDynamicRouteSegment(route) { return /\[(\.\.\.)?.+?\]/.test(route) } - Separate route type from render mode
Instead of one label, compute two pieces of metadata:
- routeType: static-route or dynamic-route
- renderMode: static, dynamic, or ISR
function getRouteIndicator(route, renderMode) { const routeType = isDynamicRouteSegment(route) ? 'dynamic-route' : 'static-route' return { routeType, renderMode, } } - Update the UI label
Do not show only Static for a page like /blog/[slug]. Show a clearer label such as:
- Dynamic Route • Static Render
- Dynamic Route • ISR
- Dynamic Route • Dynamic Render
function formatIndicatorLabel(meta) { if (meta.routeType === 'dynamic-route') { if (meta.renderMode === 'static') return 'Dynamic Route • Static Render' if (meta.renderMode === 'isr') return 'Dynamic Route • ISR' return 'Dynamic Route • Dynamic Render' } if (meta.renderMode === 'static') return 'Static Route • Static Render' if (meta.renderMode === 'isr') return 'Static Route • ISR' return 'Static Route • Dynamic Render' } - Validate using representative App Router examples
Test the indicator against several route types.
app/about/page.tsx // Static route app/blog/[slug]/page.tsx // Dynamic route app/docs/[...parts]/page.tsx // Catch-all dynamic route app/shop/[[...slug]]/page.tsx // Optional catch-all dynamic route - Test both static and dynamic rendering inputs
Use examples with:
- generateStaticParams()
- export const dynamic = ‘force-dynamic’
- export const revalidate = 60
- fetch(…, { cache: ‘no-store’ })
// app/blog/[slug]/page.tsx export async function generateStaticParams() { return [{ slug: 'a' }, { slug: 'b' }] } export default function Page({ params }) { return <div>{params.slug}</div> }This route is still dynamic by segment, even if it is statically rendered for known params.
- Add regression coverage
Create tests that assert the indicator does not flatten route semantics into a single static label.
describe('route indicator', () => { it('marks [slug] as a dynamic route even when statically rendered', () => { const meta = getRouteIndicator('/blog/[slug]', 'static') expect(meta).toEqual({ routeType: 'dynamic-route', renderMode: 'static', }) }) })
If you are not patching framework internals and just need to understand the behavior in your app, the key takeaway is simple: the route is dynamic, but the rendering can still be static.
Common Edge Cases
- generateStaticParams() makes the page look static
This is expected. It precomputes known dynamic paths, but it does not turn [slug] into a non-dynamic route.
- Catch-all segments
Routes like […slug] and [[…slug]] must also be treated as dynamic segments in the indicator logic.
- Forced runtime rendering
If a page exports dynamic = ‘force-dynamic’ or uses cookies(), headers(), or uncached fetches, the render mode should switch to dynamic. The route classification should remain separate.
- ISR confusion
A route with revalidate is neither purely static nor fully runtime-rendered. Labeling it only as static can still be misleading if the route also contains params.
- Nested dynamic segments
Paths such as app/[category]/[slug]/page.tsx should not lose dynamic-route labeling just because all values are generated at build time.
- Route groups and parallel routes
Folders such as (marketing) or advanced routing structures should be ignored when detecting whether the actual URL path contains dynamic segments.
FAQ
1. Why does Next.js say a dynamic route is static?
Because the indicator is usually describing the rendering result, not the filesystem route pattern. A page in [slug] can still be pre-rendered statically.
2. Does generateStaticParams() make a dynamic route become static?
No. It keeps the route dynamic in structure but allows known parameter values to be generated at build time. The path pattern still includes a parameter segment.
3. What should the correct indicator show?
It should communicate both dimensions clearly, such as Dynamic Route • Static Render instead of only Static. That avoids ambiguity and better matches developer expectations.
If you plan to contribute a fix upstream, keep the implementation focused on label accuracy rather than altering route behavior. The underlying rendering system may already be correct; the issue is that the indicator collapses two separate concepts into one misleading status.
For framework discussions and contribution workflow, reference the relevant project guidance through the Next.js repository.