beginner Step 3 of 15

Layouts and Shared Components

Next.js Development

Layouts and Shared Components

Layouts in Next.js define shared UI structure that wraps multiple pages. The root layout is required and wraps your entire application, while nested layouts wrap specific route segments. Unlike pages, layouts preserve their state when users navigate between child routes — the layout component is not re-rendered, only the page content changes. This makes layouts ideal for persistent navigation bars, sidebars, footers, and other UI elements that should remain consistent across pages. Next.js also provides special file conventions for loading states, error handling, and not-found pages.

Root Layout

The root layout is required and must include the <html> and <body> tags. It wraps every page in your application.

// app/layout.tsx — root layout
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "My Next.js App",
  description: "A production-ready Next.js application",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Header />
        <main className="container">{children}</main>
        <Footer />
      </body>
    </html>
  );
}

Nested Layouts

Nested layouts wrap specific route segments. They are defined with a layout.tsx file inside a route folder and receive the child content as the children prop.

// app/dashboard/layout.tsx — wraps all /dashboard/* pages
import Sidebar from "@/components/Sidebar";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard-layout">
      <Sidebar
        links={[
          { href: "/dashboard", label: "Overview" },
          { href: "/dashboard/projects", label: "Projects" },
          { href: "/dashboard/settings", label: "Settings" },
        ]}
      />
      <div className="dashboard-content">{children}</div>
    </div>
  );
}

// This layout wraps:
// app/dashboard/page.tsx        → /dashboard
// app/dashboard/projects/page.tsx → /dashboard/projects
// app/dashboard/settings/page.tsx → /dashboard/settings

Loading States

A loading.tsx file creates an automatic loading UI that displays while the page content is being fetched or rendered. It leverages React Suspense under the hood.

// app/dashboard/loading.tsx
export default function DashboardLoading() {
  return (
    <div className="loading-container">
      <div className="spinner" />
      <p>Loading dashboard...</p>
    </div>
  );
}

// This loading UI appears automatically when navigating
// to any page under /dashboard while data is being fetched.

Error Handling

An error.tsx file creates an error boundary that catches errors in its child route segment. It must be a Client Component.

// app/dashboard/error.tsx
"use client";

export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="error-container">
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

Not Found Page

// app/not-found.tsx — custom 404 page
import Link from "next/link";

export default function NotFound() {
  return (
    <div className="not-found">
      <h1>404 — Page Not Found</h1>
      <p>The page you are looking for does not exist.</p>
      <Link href="/">Return Home</Link>
    </div>
  );
}

// Trigger from a page:
// import { notFound } from "next/navigation";
// if (!data) notFound();

Shared Components

// components/Header.tsx — shared across all pages via root layout
import Link from "next/link";

export default function Header() {
  return (
    <header className="header">
      <Link href="/" className="logo">MyApp</Link>
      <nav>
        <Link href="/about">About</Link>
        <Link href="/blog">Blog</Link>
        <Link href="/dashboard">Dashboard</Link>
      </nav>
    </header>
  );
}
Tip: Layouts are Server Components by default and do not re-render when navigating between child pages. This makes them efficient for persistent UI. If a layout needs interactivity (like a mobile menu toggle), extract the interactive part into a Client Component and keep the layout itself as a Server Component.

Key Takeaways

  • The root layout.tsx wraps the entire app and must include <html> and <body> tags.
  • Nested layouts wrap route segments and persist across child page navigations.
  • loading.tsx creates automatic loading UIs using React Suspense.
  • error.tsx creates error boundaries that catch and display errors gracefully.
  • not-found.tsx customizes the 404 page, triggered by notFound().