Files
greedychipmunk__agent-skills/nextjs-developer/resources/routing-reference.md
2026-01-22 10:18:31 -05:00

8.3 KiB

Next.js Routing Reference

File-Based Routing

Next.js App Router uses a file-system based router where:

  • Folders define routes
  • Files define UI

Basic Routes

Static Routes

app/
├── page.tsx          # /
├── about/
│   └── page.tsx      # /about
└── blog/
    └── page.tsx      # /blog

Nested Routes

app/
└── blog/
    ├── page.tsx           # /blog
    └── posts/
        └── page.tsx       # /blog/posts

Dynamic Routes

Single Parameter

app/
└── posts/
    └── [id]/
        └── page.tsx       # /posts/1, /posts/2, etc.
// app/posts/[id]/page.tsx
export default function PostPage({
  params,
}: {
  params: { id: string }
}) {
  return <div>Post: {params.id}</div>
}

Multiple Parameters

app/
└── shop/
    └── [category]/
        └── [product]/
            └── page.tsx   # /shop/electronics/phone
// app/shop/[category]/[product]/page.tsx
export default function ProductPage({
  params,
}: {
  params: { category: string; product: string }
}) {
  return (
    <div>
      Category: {params.category}
      Product: {params.product}
    </div>
  )
}

Catch-All Segments

app/
└── docs/
    └── [...slug]/
        └── page.tsx       # /docs/a, /docs/a/b, /docs/a/b/c
// app/docs/[...slug]/page.tsx
export default function DocsPage({
  params,
}: {
  params: { slug: string[] }
}) {
  // /docs/a/b/c -> slug = ['a', 'b', 'c']
  return <div>Path: {params.slug.join("/")}</div>
}

Optional Catch-All

app/
└── docs/
    └── [[...slug]]/
        └── page.tsx       # /docs, /docs/a, /docs/a/b

Route Groups

Groups organize routes without affecting the URL.

Syntax: (folderName)

app/
├── (marketing)/
│   ├── about/page.tsx     # /about
│   └── contact/page.tsx   # /contact
├── (shop)/
│   ├── products/page.tsx  # /products
│   └── cart/page.tsx      # /cart
└── page.tsx               # /

Multiple Layouts

app/
├── (marketing)/
│   ├── layout.tsx         # Marketing layout
│   ├── about/page.tsx
│   └── blog/page.tsx
├── (app)/
│   ├── layout.tsx         # App layout (authenticated)
│   ├── dashboard/page.tsx
│   └── settings/page.tsx
└── layout.tsx             # Root layout

Parallel Routes

Render multiple pages simultaneously in the same layout.

Syntax: @folderName

app/
├── @dashboard/
│   └── page.tsx
├── @analytics/
│   └── page.tsx
├── layout.tsx
└── page.tsx
// app/layout.tsx
export default function Layout({
  children,
  dashboard,
  analytics,
}: {
  children: React.ReactNode
  dashboard: React.ReactNode
  analytics: React.ReactNode
}) {
  return (
    <div>
      {children}
      <div className="panels">
        {dashboard}
        {analytics}
      </div>
    </div>
  )
}

Default Files

app/
├── @team/
│   ├── page.tsx           # Shown at /
│   └── settings/page.tsx  # Shown at /settings
├── @analytics/
│   ├── page.tsx
│   └── default.tsx        # Fallback when no match
└── layout.tsx

Intercepting Routes

Load a route within the current layout (modal patterns).

Convention

Pattern Matches
(.) Same level
(..) One level up
(..)(..) Two levels up
(...) Root app directory

Modal Example

app/
├── @modal/
│   └── (.)photo/
│       └── [id]/
│           └── page.tsx   # Modal view
├── photo/
│   └── [id]/
│       └── page.tsx       # Full page view
├── layout.tsx
└── page.tsx
// app/layout.tsx
export default function Layout({
  children,
  modal,
}: {
  children: React.ReactNode
  modal: React.ReactNode
}) {
  return (
    <>
      {children}
      {modal}
    </>
  )
}

Navigation

import Link from "next/link"

export function Navigation() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/posts/1">Post 1</Link>
      <Link href={{ pathname: "/posts", query: { sort: "asc" } }}>
        Posts (sorted)
      </Link>
    </nav>
  )
}

Programmatic Navigation

"use client"

import { useRouter } from "next/navigation"

export function LoginButton() {
  const router = useRouter()

  function handleLogin() {
    // ... authenticate
    router.push("/dashboard")
  }

  return <button onClick={handleLogin}>Login</button>
}

Router Methods

const router = useRouter()

router.push("/dashboard")        // Navigate to route
router.replace("/login")         // Replace current history entry
router.refresh()                 // Refresh current route
router.prefetch("/about")        // Prefetch route
router.back()                    // Go back
router.forward()                 // Go forward

Route Handlers (API Routes)

Basic Handler

// app/api/posts/route.ts
import { NextResponse } from "next/server"

export async function GET() {
  const posts = await getPosts()
  return NextResponse.json(posts)
}

export async function POST(request: Request) {
  const body = await request.json()
  const post = await createPost(body)
  return NextResponse.json(post, { status: 201 })
}

Dynamic Route Handler

// app/api/posts/[id]/route.ts
import { NextResponse } from "next/server"

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = await getPost(params.id)

  if (!post) {
    return NextResponse.json(
      { error: "Post not found" },
      { status: 404 }
    )
  }

  return NextResponse.json(post)
}

Hooks

usePathname

"use client"

import { usePathname } from "next/navigation"

export function NavLink({ href, children }) {
  const pathname = usePathname()
  const isActive = pathname === href

  return (
    <a href={href} className={isActive ? "active" : ""}>
      {children}
    </a>
  )
}

useSearchParams

"use client"

import { useSearchParams } from "next/navigation"

export function SearchResults() {
  const searchParams = useSearchParams()
  const query = searchParams.get("q")

  return <div>Searching for: {query}</div>
}

useParams

"use client"

import { useParams } from "next/navigation"

export function PostInfo() {
  const params = useParams()
  // For /posts/[id] -> params.id
  return <div>Post ID: {params.id}</div>
}

useSelectedLayoutSegment

"use client"

import { useSelectedLayoutSegment } from "next/navigation"

export function NavTabs() {
  const segment = useSelectedLayoutSegment()
  // Returns the active child segment

  return (
    <nav>
      <a className={segment === "posts" ? "active" : ""}>Posts</a>
      <a className={segment === "users" ? "active" : ""}>Users</a>
    </nav>
  )
}

Redirects

In Server Components

import { redirect } from "next/navigation"

export default async function Page() {
  const user = await getUser()

  if (!user) {
    redirect("/login")
  }

  return <div>Welcome {user.name}</div>
}

In Route Handlers

import { redirect } from "next/navigation"

export async function GET(request: Request) {
  const session = await getSession()

  if (!session) {
    redirect("/login")
  }

  // ...
}

Permanent Redirect

import { permanentRedirect } from "next/navigation"

export default function Page() {
  permanentRedirect("/new-location")
}

Middleware

// middleware.ts (at project root)
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  // Check auth
  const token = request.cookies.get("token")

  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ["/dashboard/:path*", "/api/:path*"],
}

Last Updated: January 2026