Alex H Hadik
Alex H Hadik

Reputation: 796

How can I get dynamic routing properties in a template.tsx file in NextJS?

I have a file architecture as follows:

projects
|
 -page.tsx
 -[id]
  |
   -template.tsx
   -overview
    |
     -page.tsx
   -settings
    |
     -page.tsx

I am using server-rendered pages.

I want to get an use the id dynamic route property in the template.tsx file. This works fine for layout.tsx, but I need to use template.tsx so that I can re-render the sub-pages on route-change.

It seems that id gets passed as a prop just fine to a layout file, but doesn't get passed to a template file. How can I reference id inside of my template.tsx file?

Upvotes: 1

Views: 85

Answers (1)

Webber
Webber

Reputation: 5514

Option 1: Using a page's subcomponent instead

If you need this ID, presumably it's for something dynamic. If that's the case you should probably not put it in template at all.

Why?

The whole point of layout.tsx and template.tsx is to allow you to create UI that is shared between routes.

A template specifically makes sure that the children are revalidated on route change. For example, so that your Suspense boundaries can display skeleton placeholders again for data that is still loading.

So what to use?

Use a regular Server Component (or Client Component) instead.

Example:

Page component (Server Component)

export default function Page({ params }) {
  const { id } = params;
  
  // Do not await, just pass the promise
  const dataPromise = fetchDataForId(id);

  return (
    <main>
      <h1>{id}</h1>
      
      {/* Pass the promise directly to the component */}
      <MyServerComponent dataPromise={dataPromise} />
    </main>
  );
}

Dynamic component (Server Component)

// app/[id]/MyServerComponent.tsx
import { use } from 'react';

type MyServerComponentProps = {
  dataPromise: Promise<any>;
};

export default function MyServerComponent({ dataPromise }: MyServerComponentProps) {
  // Use `use` to suspend until the dataPromise resolves
  const { id } = use(dataPromise);

  return (
    <div>
      <h2>{id}</h2>
    </div>
  );
}

Example output:

To illustrate this, here's some simplified output (not code).

  // Pseudo, not actual code
  <Layout> /* server component */
    <Template> /* server component */
      <Page> /* server component */
        <h1>{id}</h1>
        <SomeOptionalClientComponent>
          <SomeServerComponent id={id}>
        </SomeOptionalClientComponent>
      </Page>
    </Template>
  </Layout>

In short, why?

  1. As long as you use composition, each nested Server Component will be rendered on the server, even if a parent Client Component exposes that Server Component as children.
  2. The Template then does not delay the initial payload based on dynamic data. Which is crucial for page load speed (as it allows the client to start loading fonts, scripts and hydrate, while the server streams in the rest).
  3. This is based on the the intended signature for partial prerendering (PPR) as explained by Next.js core dev Wyatt Johnson https://youtu.be/WLHHzsqGSVQ?t=23829

Option 2: Using a hook

In a template you can either access the id from route props or id from the segment.

// app/[id]/template.tsx
export default function Template({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  const { id } = router.query; // Access the dynamic route parameter

  return (
    <>
      <h1>{id}</h1>
      <div>{children}</div>
    </>
  )
}

or

  const segment = useSelectedLayoutSegment(); // This will give the `[id]` segment directly

Caveat: this will turn the component into a Client Component, which means it no longer renders on the server. It's probably not what you want.

Option 3: Pass prop from Layout

You can also use the layout to pass the param to the template.

This way both the Layout and the Template will remain as Server Components.

// app/[id]/layout.tsx
import Template from './template';

export default function Layout({ children, params }) {
  const { id } = params; // Extract the route parameter `id`

  return (
    <Template id={id}>
      {children}
    </Template>
  );
}

Caveat: As the whole point of layouts and templates is to share common UI across pages, at this point you probably want to use a Page or ideally one of its subcomponents instead. See template docs and a detailed explanation in Option 1 above.

Upvotes: 0

Related Questions