Next.js Cheatsheet
App Router and Routing
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.
File-System Routing Basics
Every folder in app/ that contains a page.tsx becomes a route segment.
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/:slug
└── dashboard/
├── layout.tsx → shared layout for /dashboard/*
├── page.tsx → /dashboard
└── settings/
└── page.tsx → /dashboard/settingsSpecial Files in a Segment
| File | Description |
|---|---|
page.tsx | UI for the route; makes it publicly accessible |
layout.tsx | Shared wrapper; persists across navigations |
template.tsx | Like layout but re-mounts on every navigation |
loading.tsx | Suspense fallback shown while page/data loads |
error.tsx | Error boundary (must be 'use client') |
not-found.tsx | Rendered when notFound() is called |
route.ts | API endpoint (no page.tsx in same folder) |
default.tsx | Fallback for parallel routes |
Dynamic Segments
app/blog/[slug]/page.tsx → /blog/anything app/shop/[...segments]/page.tsx → /shop/a/b/c (catch-all) app/shop/[[...segments]]/page.tsx → /shop AND /shop/a/b/c (optional catch-all)
// app/blog/[slug]/page.tsx export default async function BlogPost({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params return <h1>{slug}</h1> }
Next 15+:
paramsandsearchParamsare Promises — alwaysawaitthem.
Route Groups
Wrap a folder in (parens) to group routes without affecting the URL.
app/ ├── (marketing)/ │ ├── layout.tsx ← layout only for marketing pages │ ├── page.tsx → / │ └── about/ │ └── page.tsx → /about └── (app)/ ├── layout.tsx ← layout only for app pages └── dashboard/ └── page.tsx → /dashboard
Parallel Routes
Render multiple pages simultaneously in the same layout using named slots (@folder).
app/
└── dashboard/
├── layout.tsx
├── page.tsx
├── @analytics/
│ └── page.tsx
└── @team/
└── page.tsx// app/dashboard/layout.tsx export default function DashboardLayout({ children, analytics, team, }: { children: React.ReactNode analytics: React.ReactNode team: React.ReactNode }) { return ( <div> {children} {analytics} {team} </div> ) }
Intercepting Routes
Show a route inside the current layout without full navigation (e.g., modal).
app/ ├── feed/ │ └── page.tsx → /feed ├── photo/ │ └── [id]/ │ └── page.tsx → /photo/123 (full page) └── feed/ └── (.)photo/[id]/ → intercepts /photo/123 when navigating from /feed └── page.tsx
Convention: (.) same level · (..) one level up · (..)(..) two levels · (...) root.
<Link> Component
import Link from 'next/link' // Basic link <Link href="/about">About</Link> // Dynamic route <Link href={`/blog/${post.slug}`}>Read more</Link> // With query params <Link href={{ pathname: '/search', query: { q: 'next' } }}>Search</Link> // Replace history instead of push <Link href="/login" replace>Login</Link> // Prefetch control (default: true in viewport) <Link href="/heavy-page" prefetch={false}>Heavy Page</Link> // Scroll to top on navigation (default: true) <Link href="/page" scroll={false}>No scroll reset</Link>
useRouter (Client Component)
'use client' import { useRouter } from 'next/navigation' export function Nav() { const router = useRouter() return ( <button onClick={() => router.push('/dashboard')}>Go</button> ) }
| Method | Effect |
|---|---|
router.push(href) | Navigate; adds history entry |
router.replace(href) | Navigate; replaces history entry |
router.back() | Go back |
router.forward() | Go forward |
router.refresh() | Re-fetch current route server data |
router.prefetch(href) | Manually prefetch |
Import from
'next/navigation'(App Router), NOT'next/router'(Pages Router).
usePathname, useSearchParams, useParams
'use client' import { usePathname, useSearchParams, useParams } from 'next/navigation' function Nav() { const pathname = usePathname() // '/dashboard/settings' const searchParams = useSearchParams() const q = searchParams.get('q') // ?q=value const params = useParams() // { slug: 'my-post' } }
Wrap components using
useSearchParamsin<Suspense>to avoid build errors.
redirect and notFound (Server-side)
import { redirect, notFound } from 'next/navigation' export default async function Page() { const user = await getUser() if (!user) redirect('/login') // throws internally — no return needed if (!user.active) notFound() // renders not-found.tsx return <div>Welcome {user.name}</div> }
permanentRedirect
import { permanentRedirect } from 'next/navigation' permanentRedirect('/new-path') // 308 Permanent Redirect
Linking to External URLs
// Use a regular <a> for external links <a href="https://example.com" target="_blank" rel="noopener noreferrer"> External </a>
Active Link Styling
'use client' import Link from 'next/link' import { usePathname } from 'next/navigation' function NavLink({ href, label }: { href: string; label: string }) { const pathname = usePathname() const active = pathname === href return ( <Link href={href} aria-current={active ? 'page' : undefined} className={active ? 'font-bold' : ''} > {label} </Link> ) }