nextjs-app-router
Complete Next.js App Router fundamentals system (Next.js 15.5/16). PROACTIVELY activate for: (1) App directory structure and conventions, (2) Layouts and templates, (3) Loading and error boundaries, (4) Route groups and organization, (5) Parallel routes setup, (6) Metadata and SEO, (7) Server vs Client Components, (8) Navigation and View Transitions, (9) Async params pattern (Next.js 16 breaking change). Provides: File conventions, layout nesting, streaming UI, error handling, metadata templates, View Transitions API. Ensures correct App Router architecture with proper component boundaries.
What this skill does
## Quick Reference
| Convention | File | Purpose |
|------------|------|---------|
| Page | `page.tsx` | Route UI (required for route) |
| Layout | `layout.tsx` | Shared UI (persists across navigations) |
| Loading | `loading.tsx` | Loading UI with Suspense |
| Error | `error.tsx` | Error boundary (must be 'use client') |
| Not Found | `not-found.tsx` | 404 UI |
| Template | `template.tsx` | Re-renders on navigation |
| Pattern | Syntax | Example |
|---------|--------|---------|
| Dynamic Route | `[slug]` | `/blog/[slug]/page.tsx` |
| Catch-all | `[...slug]` | `/docs/[...slug]/page.tsx` |
| Optional Catch-all | `[[...slug]]` | `/shop/[[...slug]]/page.tsx` |
| Route Group | `(name)` | `/(marketing)/about/page.tsx` |
| Parallel Route | `@slot` | `/@analytics/page.tsx` |
## When to Use This Skill
Use for **App Router fundamentals**:
- Setting up new Next.js 15 projects with App Router
- Understanding file-based routing conventions
- Creating layouts that persist across navigation
- Implementing loading states and error boundaries
- Organizing routes with groups
**Related skills:**
- For data fetching: see `nextjs-data-fetching`
- For advanced routing: see `nextjs-routing-advanced`
- For caching strategies: see `nextjs-caching`
---
# Next.js App Router Fundamentals
## Project Structure
### App Directory
```text
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page (/)
├── loading.tsx # Loading UI
├── error.tsx # Error boundary
├── not-found.tsx # 404 page
├── global-error.tsx # Global error boundary
├── template.tsx # Re-renders on navigation
│
├── (marketing)/ # Route group (no URL impact)
│ ├── about/
│ │ └── page.tsx # /about
│ └── contact/
│ └── page.tsx # /contact
│
├── dashboard/
│ ├── layout.tsx # Dashboard layout
│ ├── page.tsx # /dashboard
│ ├── loading.tsx # Dashboard loading
│ ├── @analytics/ # Parallel route
│ │ └── page.tsx
│ ├── @team/ # Parallel route
│ │ └── page.tsx
│ └── settings/
│ └── page.tsx # /dashboard/settings
│
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/ # Dynamic route
│ ├── page.tsx # /blog/:slug
│ └── opengraph-image.tsx
│
├── products/
│ └── [...slug]/ # Catch-all route
│ └── page.tsx # /products/*
│
├── shop/
│ └── [[...slug]]/ # Optional catch-all
│ └── page.tsx # /shop or /shop/*
│
└── api/
└── users/
└── route.ts # API route handler
```
## Layouts
### Root Layout (Required)
```tsx
// app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: {
template: '%s | My App',
default: 'My App',
},
description: 'My application description',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<header>
<nav>{/* Navigation */}</nav>
</header>
<main>{children}</main>
<footer>{/* Footer */}</footer>
</body>
</html>
);
}
```
### Nested Layout
```tsx
// app/dashboard/layout.tsx
import { Sidebar } from '@/components/sidebar';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="dashboard">
<Sidebar />
<div className="dashboard-content">{children}</div>
</div>
);
}
```
### Layout with Parallel Routes
```tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<div className="dashboard">
<div className="main">{children}</div>
<div className="sidebar">
{analytics}
{team}
</div>
</div>
);
}
```
## Pages
### Basic Page
```tsx
// app/page.tsx
export default function HomePage() {
return (
<div>
<h1>Welcome to My App</h1>
<p>This is the home page.</p>
</div>
);
}
```
### Async Server Component Page
```tsx
// app/posts/page.tsx
import { db } from '@/lib/db';
async function getPosts() {
return db.posts.findMany({
orderBy: { createdAt: 'desc' },
});
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/posts/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
</div>
);
}
```
### Dynamic Route Page
```tsx
// app/posts/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { db } from '@/lib/db';
interface PageProps {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: PageProps) {
const { slug } = await params;
const post = await db.posts.findUnique({ where: { slug } });
if (!post) {
return { title: 'Post Not Found' };
}
return {
title: post.title,
description: post.excerpt,
};
}
export async function generateStaticParams() {
const posts = await db.posts.findMany({ select: { slug: true } });
return posts.map((post) => ({ slug: post.slug }));
}
export default async function PostPage({ params }: PageProps) {
const { slug } = await params;
const post = await db.posts.findUnique({ where: { slug } });
if (!post) {
notFound();
}
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
```
## Loading UI
### Loading Component
```tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
<div className="loading-container">
<div className="spinner" />
<p>Loading dashboard...</p>
</div>
);
}
```
### Skeleton Loading
```tsx
// app/posts/loading.tsx
export default function PostsLoading() {
return (
<div className="posts-skeleton">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="post-skeleton">
<div className="skeleton-title" />
<div className="skeleton-excerpt" />
</div>
))}
</div>
);
}
```
### Streaming with Suspense
```tsx
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { Analytics } from './analytics';
import { RecentSales } from './recent-sales';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<AnalyticsSkeleton />}>
<Analytics />
</Suspense>
<Suspense fallback={<SalesSkeleton />}>
<RecentSales />
</Suspense>
</div>
);
}
```
## Error Handling
### Error Boundary
```tsx
// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div className="error-container">
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
```
### Global Error Boundary
```tsx
// app/global-error.tsx
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
);
}
```
### Not Found Page
```tsx
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
<div className="not-found">
<h2>Related in Ads & Marketing
ads
IncludedMulti-platform paid advertising audit and optimization skill. Analyzes Google, Meta, YouTube, LinkedIn, TikTok, Microsoft, and Apple Ads. 250+ checks with scoring, parallel agents, industry templates, and AI creative generation.
banana
IncludedAI image generation Creative Director powered by Google Gemini Nano Banana models. Use this skill for ANY request involving image creation, editing, visual asset production, or creative direction. Triggers on: generate an image, create a photo, edit this picture, design a logo, make a banner, visual for my anything, and all /banana commands. Handles text-to-image, image editing, multi-turn creative sessions, batch workflows, and brand presets.
rpg-migration-analyzer
IncludedAnalyzes legacy RPG (Report Program Generator) programs from AS/400 and IBM i systems for migration to modern Java applications. Extracts business logic from RPG III/IV/ILE source code, identifies data structures (D-specs), file operations (F-specs), program dependencies (CALLB/CALLP), and converts RPG constructs to Java equivalents. Generates migration reports, complexity estimates, and Java implementation strategies with POJO classes, JPA entities, and service methods. Use when modernizing AS/400 or IBM i legacy systems, analyzing RPG source files (.rpg, .rpgle, .RPGLE), converting RPG to Java, mapping data specifications to Java classes, planning legacy system migration, or when user mentions RPG analysis, Report Program Generator, RPG III/IV/ILE, AS/400 modernization, IBM i migration, packed decimal conversion, or mainframe application rewrite.
brand-library-architect
IncludedBuild a complete brand library for a product — visual asset render pipeline, brand documentation set (BRAND, COPY, MANIFESTO, BIOS, FAQ, GLOSSARY, TONE, PRICING), open-source convention files (README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT), and a self-contained press kit. This skill should be used when the user asks to "build a brand library / brand kit / press kit / brand assets" for a product, "set up a brand library workflow," "create a positioning manifesto plus visual identity," or any combination of brand documentation + visual asset pipeline. Apply phase-by-phase or run end-to-end. Templates are product-agnostic and use {{TOKEN}} placeholders the skill prompts the user to fill.
writing-tech-post
IncludedAuthors engineering blog posts end-to-end: launch deep-dives, incident postmortems, architecture migrations, performance case studies, tutorials, AI/agent system writeups, security disclosures, and research-to-product translations. Picks the correct archetype, plans the abstraction ladder, enforces an evidence cadence (diagrams, benchmarks, profiles, traces, code, ablations), tunes voice against publisher house styles (Datadog, Vercel, GitHub, AWS, Meta, Cloudflare, Jane Street), and runs a pre-publish gate for narrative momentum and disclosure ethics. Use when drafting a new engineering post, restructuring a draft that feels flat, deciding which evidence form belongs where, validating that depth and product context are balanced, or preparing a postmortem, migration, or performance narrative for external publication. Do not use for API reference documentation, README authoring, marketing copy, release notes, generic SEO content, ghost-written executive thought leadership, or non-engineering long-form essays.
blog-google
IncludedGoogle API integration for blog performance: PageSpeed Insights, CrUX Core Web Vitals with 25-week history, Search Console performance, URL Inspection, Indexing API, GA4 organic traffic, NLP entity analysis for E-E-A-T, YouTube video search for embedding, and Google Ads Keyword Planner. Progressive feature availability based on credential tier (API key, OAuth/service account, GA4, Ads). Shares config with claude-seo at ~/.config/claude-seo/google-api.json. Use when user says "google data", "page speed", "core web vitals", "search console", "indexation", "GA4", "keyword research", "nlp entities", "blog performance", "youtube search", "google api setup".