Next.js Cheatsheet

Pages and Layouts

Use this Next.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

page.tsx — Route UI

The default export from page.tsx renders for the route URL.

// app/about/page.tsx  →  /about
export default function AboutPage() {
  return <main>About us</main>
}

Async page (data fetching):

// app/blog/[slug]/page.tsx
export default async function BlogPost({
  params,
  searchParams,
}: {
  params: Promise<{ slug: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
  const { slug } = await params
  const { page } = await searchParams

  const post = await fetchPost(slug)
  return <article>{post.body}</article>
}

In Next.js 15+, both params and searchParams are Promises.

layout.tsx — Persistent Wrapper

Layouts wrap child routes and do not re-mount on navigation — state is preserved.

// app/layout.tsx  (Root layout — required, must include <html> and <body>)
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My App',
  description: 'My Next.js app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Nested layout (no <html> / <body>):

// app/dashboard/layout.tsx  →  wraps all /dashboard/* routes
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">{children}</main>
    </div>
  )
}

Layout Nesting Chain

app/layout.tsx          ← root (wraps everything)
  app/dashboard/
    layout.tsx          ← dashboard layout (wraps /dashboard/*)
      app/dashboard/
        settings/
          layout.tsx    ← settings layout (wraps /dashboard/settings/*)
            page.tsx    ← actual page content

template.tsx — Re-mounting Wrapper

Same as layout.tsx but creates a new instance on each navigation (state resets, effects re-run). Useful for animations or analytics page-view tracking.

// app/dashboard/template.tsx
export default function DashboardTemplate({
  children,
}: {
  children: React.ReactNode
}) {
  return <div className="animate-fade-in">{children}</div>
}

loading.tsx — Streaming Suspense UI

Automatically wraps the page in <Suspense>. Shown while the page component or its data loads.

// app/dashboard/loading.tsx
export default function Loading() {
  return <div className="spinner" aria-label="Loading…" />
}

Next.js streams the layout + loading UI immediately; the page replaces it when ready.

error.tsx — Error Boundary

Must be a Client Component. Catches errors thrown during rendering or data fetching in the segment.

// app/dashboard/error.tsx
'use client'

import { useEffect } from 'react'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    console.error(error)
  }, [error])

  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

reset() re-renders the segment. error.digest is a hashed server error ID safe to log/display.

not-found.tsx — 404 UI

Rendered when notFound() is called in the same segment (or nested).

// app/not-found.tsx   ← global 404
export default function NotFound() {
  return (
    <section>
      <h2>Page not found</h2>
      <a href="/">Go home</a>
    </section>
  )
}

Scoped 404:

// app/blog/[slug]/not-found.tsx
export default function PostNotFound() {
  return <p>This post does not exist.</p>
}

Triggering it:

import { notFound } from 'next/navigation'

const post = await db.post.findUnique({ where: { slug } })
if (!post) notFound()

global-error.tsx — Root Error Boundary

Catches errors in the root layout.tsx. Must include its own <html> and <body>.

// app/global-error.tsx
'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>Something went catastrophically wrong</h2>
        <button onClick={reset}>Try again</button>
      </body>
    </html>
  )
}

Route Group Layouts

Use (group) folders to apply different layouts to disjoint URL groups.

app/
├── (auth)/
│   ├── layout.tsx       ← minimal layout for sign-in/sign-up
│   ├── login/page.tsx   →  /login
│   └── signup/page.tsx  →  /signup
└── (main)/
    ├── layout.tsx       ← full app shell layout
    └── dashboard/
        └── page.tsx     →  /dashboard

default.tsx — Parallel Route Fallback

Used with parallel routes (@slot). Renders when a slot has no matching route during soft navigation.

// app/dashboard/@analytics/default.tsx
export default function AnalyticsDefault() {
  return null   // render nothing if no analytics match
}

Metadata in Pages and Layouts

Static metadata export (works in page.tsx and layout.tsx):

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Dashboard',
  description: 'Your personal dashboard',
}

Dynamic metadata:

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>
}): Promise<Metadata> {
  const { slug } = await params
  const post = await fetchPost(slug)
  return { title: post.title, description: post.excerpt }
}

TypeScript Helpers (PageProps, LayoutProps)

Next 15.5+ generates globally available route-typed helpers (no import needed) — pass the route literal and params/searchParams are typed for you:

// app/blog/[slug]/page.tsx
export default async function Page(props: PageProps<'/blog/[slug]'>) {
  const { slug } = await props.params            // typed as string
  const { page } = await props.searchParams
  return <div>{slug}</div>
}
// app/dashboard/layout.tsx
export default function Layout(props: LayoutProps<'/dashboard'>) {
  return <section>{props.children}</section>     // parallel route slots are typed too
}

Types are generated by next dev / next build, or on demand with npx next typegen. Manual typing still works if you prefer:

type Props = { params: Promise<{ slug: string }> }

export default async function Page({ params }: Props) {
  const { slug } = await params
  return <div>{slug}</div>
}