template.js
Wraps a segment like a layout but remounts on navigation, resetting state and re-firing effects.
export default function Template({
children,
}: {
children: React.ReactNode
}): React.ReactNode- Added in Next.js v13.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$children | React.ReactNode | The layout or page this template wraps. Next.js renders the template between layout.js and its children and assigns it a unique key per segment. |
Return value
React.ReactNode | Promise<React.ReactNode>The wrapping UI, remounted with a fresh key whenever the segment it belongs to changes.
Changelog
- PHP v13.0.0template introduced.
A template wraps a layout or page just as layout.js does, with one difference that changes everything: Next.js gives it a unique key per navigation, so it remounts. New DOM nodes, client state back to its initial value, useEffect re-running. It renders between layout.js and error.js, wrapping error.js, loading.js, not-found.js and page.js, but not the layout in its own segment.
Reach for it when persistence is the bug: an enter animation that should play on every route change, a per-page analytics useEffect that must fire again, a filter form that ought to come back empty. Everything else should stay a layout — remounting throws away the work layouts exist to preserve.
What trips people up
- It remounts per segment level, not per URL. The template's key tracks its own segment, so navigating deeper does not remount templates above.
app/template.tsxsurvives a move from/blog/ato/blog/b;app/blog/template.tsxdoes not. - Search params never trigger a remount. Changing
?sort=desckeeps the same key. - A template does not replace a layout. Both can exist in a segment and both then render.
- Suspense fallbacks inside a template show on every navigation, not only the first — sometimes the reason to use one, often a surprise.
Example
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}When to use it
- Play a fade-in animation each time the user moves to a different post
- Fire a page-view analytics effect on every navigation, not just the first mount
- Clear a search or filter input when the user switches to another route
- Show a Suspense fallback on every navigation instead of only the first load
Frequently asked questions
What is the difference between template.js and layout.js?
A layout persists across navigation: it is not re-rendered, so client state, scroll position and mounted effects survive. A template is given a unique key and remounts when its segment changes, so the DOM is recreated, client state resets and effects re-run. Use a layout by default and a template only when resetting is the behaviour you want.
Why is my template not remounting on navigation?
Because template keys track their own segment level, not the full URL. A root app/template.tsx keeps its key while you move within the same first segment, and only remounts when that segment changes. Search params never cause a remount either, so a transition from ?page=1 to ?page=2 leaves state intact. Put the template in the segment whose changes you care about.
Can I have both a layout.js and a template.js in the same folder?
Yes, and both render — Next.js nests the template inside the layout, so the output is <Layout><Template key={...}>{children}</Template></Layout>. That pairing is the usual setup: persistent chrome in the layout, remount-on-navigation behaviour such as enter animations in the template.
More examples
An effect that must re-fire on every navigation
In a layout this effect would run once and never again, because layouts are not remounted. As a template the component gets a new key per navigation, remounts, and the effect re-synchronises — one tracked view per route change. Note that ?sort=desc style changes do not remount it, so query-only transitions are not counted.
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
import { trackPageView } from '@/app/lib/analytics'
export default function Template({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
useEffect(() => {
trackPageView(pathname)
}, [pathname])
return <>{children}</>
}How keys change as you navigate
This is the rendered shape, not code you write, and the key values are illustrative. It shows why remounting is per segment level: the first segment stayed /blog, so the root template kept its key and survived, while the blog template's child segment changed and it remounted. A root-level template does not reset on every navigation within the same top segment.
// Route tree:
// app/template.tsx (root template)
// app/blog/template.tsx (blog template)
// app/blog/[slug]/page.tsx
// At /blog
<RootLayout>
<Template key="/blog">
<Template key="/blog">
<BlogIndexPage />
</Template>
</Template>
</RootLayout>
// After navigating to /blog/first-post
<RootLayout>
<Template key="/blog">
{/* root template key unchanged — it does NOT remount */}
<Template key="/blog/first-post">
{/* blog template key changed — it remounts */}
<BlogPostPage slug="first-post" />
</Template>
</Template>
</RootLayout>Template and layout side by side
Both files can live in the same folder and both render — the template goes inside the layout. The sidebar keeps its scroll position and any open menu across posts, while the fade-in wrapper is recreated for each one, so the CSS animation restarts every time. Using one file for both jobs is what forces the usual compromise.
// app/blog/layout.tsx — persists across posts
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<BlogSidebar />
{children}
</div>
)
}
// app/blog/template.tsx — remounts on each post
export default function BlogTemplate({
children,
}: {
children: React.ReactNode
}) {
return <div className="fade-in">{children}</div>
}
Discussion