nextjs-app-router
Use when next.js App Router with layouts, loading states, and streaming. Use when building modern Next.js 13+ applications.
What this skill does
# Next.js App Router
Master the Next.js App Router for building modern, performant web
applications with server components and advanced routing.
## App Directory Structure
The app directory uses file-system based routing with special files:
```typescript
app/
layout.tsx # Root layout (required)
page.tsx # Home page
loading.tsx # Loading UI
error.tsx # Error UI
not-found.tsx # 404 UI
template.tsx # Re-rendered layout
about/
page.tsx # /about
blog/
layout.tsx # Blog-specific layout
page.tsx # /blog
loading.tsx # Blog loading state
[slug]/
page.tsx # /blog/[slug]
dashboard/
(auth)/ # Route group (doesn't affect URL)
layout.tsx # Layout for auth routes
settings/
page.tsx # /dashboard/settings
profile/
page.tsx # /dashboard/profile
// app/layout.tsx - Root layout (required)
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<header>
<Navigation />
</header>
<main>{children}</main>
<footer>
<Footer />
</footer>
</body>
</html>
);
}
```
## Layouts: Root, Nested, and Templates
```typescript
// app/layout.tsx - Root Layout (wraps entire app)
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'My App',
description: 'Built with Next.js App Router'
};
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<Providers>
<Navigation />
{children}
</Providers>
</body>
</html>
);
}
// app/dashboard/layout.tsx - Nested Layout
export default function DashboardLayout({
children
}: {
children: React.ReactNode
}) {
return (
<div className="dashboard">
<aside>
<DashboardNav />
</aside>
<section>{children}</section>
</div>
);
}
// app/dashboard/template.tsx - Template (re-renders on navigation)
// Use when you need fresh state on each navigation
export default function DashboardTemplate({
children
}: {
children: React.ReactNode
}) {
// This re-renders and resets state on navigation
return <div className="animate-fade-in">{children}</div>;
}
```
## Dynamic Routes and generateStaticParams
```typescript
// app/blog/[slug]/page.tsx
interface PageProps {
params: { slug: string };
searchParams: { [key: string]: string | string[] | undefined };
}
export default async function BlogPost({ params }: PageProps) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// Generate static paths at build time (SSG)
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug
}));
}
// Generate metadata dynamically
export async function generateMetadata({ params }: PageProps) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [{ url: post.image }]
}
};
}
// Multiple dynamic segments: app/shop/[category]/[product]/page.tsx
export default async function Product({
params
}: {
params: { category: string; product: string }
}) {
const product = await getProduct(params.category, params.product);
return <div>{product.name}</div>;
}
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({
category: product.category,
product: product.slug
}));
}
// Catch-all routes: app/docs/[...slug]/page.tsx
export default function Docs({ params }: { params: { slug: string[] } }) {
// /docs/a/b/c -> params.slug = ['a', 'b', 'c']
const path = params.slug.join('/');
return <div>Documentation: {path}</div>;
}
// Optional catch-all: app/blog/[[...slug]]/page.tsx
// Matches both /blog and /blog/a/b/c
```
## Loading UI and Streaming
```typescript
// app/blog/loading.tsx - Automatic loading UI
export default function Loading() {
return (
<div className="loading">
<Skeleton />
<Skeleton />
<Skeleton />
</div>
);
}
// app/blog/page.tsx - Server component with streaming
import { Suspense } from 'react';
export default function BlogPage() {
return (
<div>
<h1>Blog</h1>
{/* Stream this component independently */}
<Suspense fallback={<PostsSkeleton />}>
<BlogPosts />
</Suspense>
{/* Stream this separately */}
<Suspense fallback={<CommentsSkeleton />}>
<RecentComments />
</Suspense>
</div>
);
}
// Components can stream as they load
async function BlogPosts() {
const posts = await getPosts(); // Server-side data fetch
return (
<div>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
async function RecentComments() {
const comments = await getComments();
return (
<div>
{comments.map(comment => (
<CommentCard key={comment.id} comment={comment} />
))}
</div>
);
}
```
## Error Boundaries and Error Handling
```typescript
// app/blog/error.tsx - Error boundary
'use client'; // Error components must be Client Components
import { useEffect } from 'react';
export default function Error({
error,
reset
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log error to error reporting service
console.error('Blog error:', error);
}, [error]);
return (
<div className="error-container">
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
// app/global-error.tsx - Global error boundary
'use client';
export default function GlobalError({
error,
reset
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<h2>Application Error</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
);
}
// app/blog/not-found.tsx - Custom 404 page
import Link from 'next/link';
export default function NotFound() {
return (
<div>
<h2>Post Not Found</h2>
<p>Could not find the requested blog post.</p>
<Link href="/blog">View all posts</Link>
</div>
);
}
// Programmatically trigger 404
import { notFound } from 'next/navigation';
export default async function Post({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
if (!post) {
notFound(); // Renders closest not-found.tsx
}
return <article>{post.content}</article>;
}
```
## Route Groups for Organization
```typescript
// Route groups don't affect URL structure
app/
(marketing)/ # Group routes without affecting URLs
layout.tsx # Marketing layout
page.tsx # / (homepage)
about/
page.tsx # /about
contact/
page.tsx # /contact
(shop)/
layout.tsx # Shop layout
products/
page.tsx # /products
cart/
page.tsx # /cart
(auth)/
layout.tsx # Auth layout
login/
page.tsx # /login
register/
page.tsx # /register
// app/(marketing)/layout.tsx
export default function MarketingLayout({
children
}: {
children: React.ReactNode
}) {
return (
<>
<MarketingHeader />
{children}
<MarketingFooter />
</>
);
}
// app/(shop)/layout.tsx
export default function ShopLayout({
children
}: {
children: React.ReactNode
}) {
return (
<>
<ShopHeader />
<ShopNav />
{children}
</>
);
}
```
#Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.