πŸ“‹ Cheat Sheets
Β· 4 min read

Next.js Cheat Sheet β€” Routing, Data Fetching, and File Conventions


Some links in this article are affiliate links. We earn a commission at no extra cost to you when you purchase through them. Full disclosure.

Click any item to expand the explanation and examples.

πŸ“ File Conventions (App Router)

page.tsx β€” route page files
Every route needs a page.tsx. The file path = the URL.
app/page.tsx              β†’ /
app/about/page.tsx        β†’ /about
app/blog/page.tsx         β†’ /blog
app/blog/[slug]/page.tsx  β†’ /blog/my-post
// app/page.tsx
export default function Home() {
  return <h1>Home</h1>;
}
layout.tsx β€” shared layout files
Wraps all pages in the same directory and below. Does NOT re-render on navigation.
// app/layout.tsx (root layout β€” required)
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

// app/dashboard/layout.tsx (nested layout) export default function DashboardLayout({ children }) { return ( <div> <nav>Dashboard Nav</nav> {children} </div> ); }

loading.tsx, error.tsx, not-found.tsx files
// app/dashboard/loading.tsx β€” shows while page loads
export default function Loading() {
  return <div>Loading...</div>;
}

// app/dashboard/error.tsx β€” catches errors β€˜use client’; export default function Error({ error, reset }) { return ( <div> <p>Something went wrong: {error.message}</p> <button onClick={reset}>Try again</button> </div> ); }

// app/not-found.tsx β€” custom 404 export default function NotFound() { return <h1>Page not found</h1>; }

route.ts β€” API routes files
// app/api/users/route.ts
import { NextResponse } from 'next/server';

export async function GET() { const users = await db.user.findMany(); return NextResponse.json(users); }

export async function POST(request: Request) { const body = await request.json(); const user = await db.user.create({ data: body }); return NextResponse.json(user, { status: 201 }); }

Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.

πŸ”€ Routing

Dynamic routes β€” [slug] routing
// app/blog/[slug]/page.tsx
export default async function Post({ params }) {
  const { slug } = await params;
  return <h1>Post: {slug}</h1>;
}

// Catch-all: app/docs/[…slug]/page.tsx // Matches /docs/a, /docs/a/b, /docs/a/b/c export default async function Docs({ params }) { const { slug } = await params; // [β€˜a’, β€˜b’, β€˜c’] }

// Optional catch-all: app/docs/[[…slug]]/page.tsx // Also matches /docs (without any slug)

Route groups β€” (folder) routing
Parentheses in folder names are ignored in the URL. Use for organizing layouts.
app/(marketing)/about/page.tsx   β†’ /about
app/(marketing)/pricing/page.tsx β†’ /pricing
app/(app)/dashboard/page.tsx     β†’ /dashboard

Each group can have its own layout

app/(marketing)/layout.tsx app/(app)/layout.tsx

Parallel & intercepting routes routing
# Parallel routes β€” render multiple pages in same layout
app/@modal/login/page.tsx
app/layout.tsx  β†’ receives { children, modal } props

Intercepting routes β€” show route in modal

app/feed/@modal/(.)photo/[id]/page.tsx

(.) = same level, (..) = one level up, (…) = root

πŸ“‘ Data Fetching

Server Components β€” fetch in components data
Components are Server Components by default. Just use async/await.
// app/users/page.tsx (Server Component)
export default async function UsersPage() {
  const res = await fetch('https://api.example.com/users');
  const users = await res.json();

return ( <ul> {users.map(u => <li key={u.id}>{u.name}</li>)} </ul> ); }

Caching & revalidation data
// Cache forever (default)
fetch('https://api.example.com/data');

// Revalidate every 60 seconds fetch(β€˜https://api.example.com/data’, { next: { revalidate: 60 } });

// No cache (always fresh) fetch(β€˜https://api.example.com/data’, { cache: β€˜no-store’ });

// Page-level revalidation export const revalidate = 60;

// On-demand revalidation (in a Server Action or Route Handler) import { revalidatePath, revalidateTag } from β€˜next/cache’; revalidatePath(β€˜/blog’); revalidateTag(β€˜posts’);

Server Actions data
// app/actions.ts
'use server';

export async function createPost(formData: FormData) { const title = formData.get(β€˜title’); await db.post.create({ data: { title } }); revalidatePath(β€˜/blog’); }

// In a component import { createPost } from ’./actions’;

export default function NewPost() { return ( <form action={createPost}> <input name=β€œtitle” /> <button type=β€œsubmit”>Create</button> </form> ); }

🧩 Common Patterns

'use client' β€” Client Components pattern
Add 'use client' at the top when you need interactivity.
'use client';

import { useState } from β€˜react’;

export default function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; }

You need β€˜use client’ for: useState, useEffect, event handlers, browser APIs.

Metadata & SEO pattern
// Static metadata
export const metadata = {
  title: 'My Page',
  description: 'Page description',
  openGraph: { title: 'My Page', images: ['/og.png'] },
};

// Dynamic metadata export async function generateMetadata({ params }) { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt }; }

Middleware pattern
// middleware.ts (in project root)
import { NextResponse } from 'next/server';

export function middleware(request) { // Redirect if (request.nextUrl.pathname === β€˜/old’) { return NextResponse.redirect(new URL(β€˜/new’, request.url)); }

// Rewrite if (request.nextUrl.pathname.startsWith(β€˜/api’)) { return NextResponse.rewrite(new URL(β€˜/api/v2’ + request.nextUrl.pathname, request.url)); }

return NextResponse.next(); }

export const config = { matcher: [β€˜/old’, β€˜/api/:path*’], };

[Environment variables](/blog/what-is-environment-variables/) pattern
# .env.local
DATABASE_URL=[postgresql](/blog/what-is-postgresql/)://...
NEXT_PUBLIC_API_URL=https://api.example.com

Server-only (no prefix)

process.env.DATABASE_URL

Client-accessible (NEXT_PUBLIC_ prefix)

process.env.NEXT_PUBLIC_API_URL

Only variables prefixed with NEXT_PUBLIC_ are available in the browser. Everything else is server-only.

Quick access: Raycast lets you search commands, snippets, and cheat sheets instantly from your keyboard. Free for Mac.

Related: What is Next.js Β· React Interview Questions

πŸ“˜