template.js

Wraps a segment like a layout but remounts on navigation, resetting state and re-firing effects.

Signature
export default function Template({
  children,
}: {
  children: React.ReactNode
}): React.ReactNode
  • Added in Next.js v13.0.0

Parameters

ParameterTypeDescription
$childrenReact.ReactNodeThe 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.tsx survives a move from /blog/a to /blog/b; app/blog/template.tsx does not.
  • Search params never trigger a remount. Changing ?sort=desc keeps 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

Example · typescript
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.

Example · typescript
'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.

Example · typescript
// 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.

Example · typescript
// 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

  • Be the first to comment on this lesson.