Nextjs App Router Fundamentals
Guide for working with Next.js App Router (Next.js 13+). Use when migrating from Pages Router to App Router, creating layouts, implementing routing, handling metadata, or building Next.js 13+ applications. Activates for App Router migration, layout creation, routing patterns, or Next.js 13+ development tasks.
- Skill ID
- wsimmonds/claude-nextjs-skills/nextjs-app-router-fundamentals
- Publisher
- wsimmonds
- Repository
- claude-nextjs-skills
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
wsimmonds/claude-nextjs-skills/nextjs-app-router-fundamentalsInstalls these files- SKILL.md
What this skill tells the agent
Next.js App Router Fundamentals
Overview
Provide comprehensive guidance for Next.js App Router (Next.js 13+), covering migration from Pages Router, file-based routing conventions, layouts, metadata handling, and modern Next.js patterns.
TypeScript: NEVER Use any Type
CRITICAL RULE: This codebase has @typescript-eslint/no-explicit-any enabled. Using any will cause build failures.
❌ WRONG:
function handleSubmit(e: any) { ... }
const data: any[] = [];✅ CORRECT:
function handleSubmit(e: React.FormEvent<HTMLFormElement>) { ... }
const data: string[] = [];Common Next.js Type Patterns
// Page props
function Page({ params }: { params: { slug: string } }) { ... }
function Page({ searchParams }: { searchParams: { [key: string]: string | string[] | undefined } }) { ... }
// Form events
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { ... }
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { ... }
// Server actions
async function myAction(formData: FormData) { ... }When to Use This Skill
Use this skill when:
- Migrating from Pages Router (
pages/directory) to App Router (app/directory) - Creating Next.js 13+ applications from scratch
- Working with layouts, templates, and nested routing
- Implementing metadata and SEO optimizations
- Building with App Router routing conventions
- Handling route groups, parallel routes, or intercepting routes basics
Core Concepts
App Router vs Pages Router
Pages Router (Legacy - Next.js 12 and earlier):
pages/
├── index.tsx # Route: /
├── about.tsx # Route: /about
├── _app.tsx # Custom App component
├── _document.tsx # Custom Document component
└── api/ # API routes
└── hello.ts # API endpoint: /api/helloApp Router (Modern - Next.js 13+):
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Route: /
├── about/ # Route: /about
│ └── page.tsx
├── blog/
│ ├── layout.tsx # Nested layout
│ └── [slug]/
│ └── page.tsx # Dynamic route: /blog/:slug
└── api/ # Route handlers
└── hello/
└── route.ts # API endpoint: /api/helloFile Conventions
Special Files in App Router:
layout.tsx- Shared UI for a segment and its children (preserves state, doesn't re-render)page.tsx- Unique UI for a route, makes route publicly accessibleloading.tsx- Loading UI with React Suspenseerror.tsx- Error UI with Error Boundariesnot-found.tsx- 404 UItemplate.tsx- Similar to layout but re-renders on navigationroute.ts- API endpoints (Route Handlers)
Colocation:
- Components, tests, and other files can be colocated in
app/ - Only
page.tsxandroute.tsfiles create public routes - Other files (components, utils, tests) are NOT routable
Migration Guide: Pages Router to App Router
Step 1: Understand the Current Structure
Examine existing Pages Router setup:
- Read
pages/directory structure - Identify
_app.tsx- handles global state, layouts, providers - Identify
_document.tsx- customizes HTML structure - Note metadata usage (
next/head,<Head>component) - List all routes and dynamic segments
Step 2: Create Root Layout
Create app/layout.tsx - REQUIRED for all App Router applications:
// app/layout.tsx
export const metadata = {
title: 'My App',
description: 'App description',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Migration Notes:
- Move
_document.tsxHTML structure tolayout.tsx - Move
_app.tsxglobal providers/wrappers tolayout.tsx - Convert
<Head>metadata tometadataexport - The root layout MUST include
<html>and<body>tags
Step 3: Migrate Pages to Routes
Simple Page Migration:
// Before: pages/index.tsx
import Head from 'next/head';
export default function Home() {
return (
<>
<Head>
<title>Home Page</title>
</Head>
<main>
<h1>Welcome</h1>
</main>
</>
);
}// After: app/page.tsx
export default function Home() {
return (
<main>
<h1>Welcome</h1>
</main>
);
}
// Metadata moved to layout.tsx or exported here
export const metadata = {
title: 'Home Page',
};Nested Route Migration:
// Before: pages/blog/[slug].tsx
export default function BlogPost() { ... }// After: app/blog/[slug]/page.tsx
export default function BlogPost() { ... }Step 4: Update Navigation
Replace anchor tags with Next.js Link:
// Before (incorrect in App Router)
<a href="/about">About</a>
// After (correct)
import Link from 'next/link';
<Link href="/about">About</Link>Step 5: Clean Up Pages Directory
After migration:
- Remove all page files from
pages/directory - Keep
pages/api/if you're not migrating API routes yet - Remove
_app.tsxand_document.tsx(functionality moved to layout) - Optionally delete empty
pages/directory
Metadata Handling
Static Metadata
// app/page.tsx or app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'My Page',
description: 'Page description',
keywords: ['nextjs', 'react'],
openGraph: {
title: 'My Page',
description: 'Page description',
images: ['/og-image.jpg'],
},
};Dynamic Metadata
// app/blog/[slug]/page.tsx
export async function generateMetadata({
params
}: {
params: { slug: string }
}): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
};
}Layouts and Nesting
Creating Nested Layouts
// app/layout.tsx - Root layout
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header />
{children}
<Footer />
</body>
</html>
);
}
// app/blog/layout.tsx - Blog layout
export default function BlogLayout({ children }) {
return (
<div>
<BlogSidebar />
<main>{children}</main>
</div>
);
}Layout Behavior:
- Layouts preserve state across navigation
- Layouts don't re-render on route changes
- Parent layouts wrap child layouts
- Root layout is required and wraps entire app
Routing Patterns
Dynamic Routes
// app/blog/[slug]/page.tsx
export default function BlogPost({
params
}: {
params: { slug: string }
}) {
return <article>Post: {params.slug}</article>;
}Catch-All Routes
// app/shop/[...slug]/page.tsx - Matches /shop/a, /shop/a/b, etc.
export default function Shop({
params
}: {
params: { slug: string[] }
}) {
return <div>Path: {params.slug.join('/')}</div>;
}Optional Catch-All
// app/shop/[[...slug]]/page.tsx - Matches /shop AND /shop/a, /shop/a/bRoute Groups
Group routes without affecting URL:
app/
├── (marketing)/
│ ├── about/
│ │ └── page.tsx # /about
│ └── contact/
│ └── page.tsx # /contact
└── (shop)/
└── products/
└── page.tsx # /productsCommon Migration Pitfalls
Pitfall 1: Forgetting Root Layout HTML Tags
Wrong:
export default function RootLayout({ children }) {
return <div>{children}</div>; // Missing <html> and <body>
}Correct:
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Pitfall 2: Using next/head in App Router
Wrong:
import Head from 'next/head';
export default function Page() {
return (
<>Use this skill in a project
Open any RiverX project, search the Skills panel for wsimmonds/claude-nextjs-skills/nextjs-app-router-fundamentals, and install it in one click.
