Layouts and Pages in Next.js – Dynamic Routes, Nested Layouts, and Linking Between Pages

Next.js uses file-based routing to define pages and layouts. This article explains how to create root and nested layouts, dynamic routes, access search parameters, and link between pages using the built-in Link component. It also introduces route props helpers like PageProps and LayoutProps for type-safe data handling across segments.

Next.js pageroot layoutdynamic routesearchParams

~3 min read • Updated Oct 25, 2025

Creating a Page in Next.js


To create a page, add a page.tsx file inside the app directory and export a React component:


// app/page.tsx
export default function Page() {
  return <h1>Hello Next.js!</h1>
}

Creating a Root Layout


Layouts are shared UI wrappers that persist across navigation. The root layout must include <html> and <body> tags:


// app/layout.tsx
export default function DashboardLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <main>{children}</main>
      </body>
    </html>
  );
}

Creating Nested Routes


To create a route like /blog, add a blog folder inside app and include a page.tsx file:


// app/blog/page.tsx
export default async function Page() {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map((post) => (
        <Post key={post.id} post={post} />
      ))}
    </ul>
  );
}

Creating Dynamic Routes


To create a dynamic route like /blog/[slug], wrap the folder name in square brackets:


// app/blog/[slug]/page.tsx
export default async function BlogPostPage({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

Nesting Layouts


Layouts can be nested by adding a layout.tsx file inside route segments. For example, for /blog:


// app/blog/layout.tsx
export default function BlogLayout({ children }) {
  return <section>{children}</section>;
}

The root layout wraps the blog layout, which wraps the blog page and blog post page.


Accessing Search Parameters


In server components, use the searchParams prop to access query parameters:


// app/page.tsx
export default async function Page({ searchParams }) {
  const filters = (await searchParams).filters;
}

In client components, use the useSearchParams hook.


Linking Between Pages


Use the built-in <Link> component from next/link to navigate between routes:


// app/ui/post.tsx
import Link from 'next/link';

export default async function Post({ post }) {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.slug}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  );
}

Using PageProps and LayoutProps


Next.js generates global helper types for route props:


// app/blog/[slug]/page.tsx
export default async function Page(props: PageProps<'/blog/[slug]'>) {
  const { slug } = await props.params;
  return <h1>Blog post: {slug}</h1>;
}

// app/dashboard/layout.tsx
export default function Layout(props: LayoutProps<'/dashboard'>) {
  return (
    <section>
      {props.children}
      {/* If you have app/dashboard/@analytics */}
      {/* {props.analytics} */}
    </section>
  );
}

Conclusion


With page.tsx and layout.tsx files, you can build dynamic, nested, and linkable routes in Next.js. These conventions make your application more flexible, readable, and maintainable.


Written & researched by Dr. Shahin Siami