plugins/nextjs-master/skills/nextjs-routing-advanced/SKILL.md
Complete Next.js advanced routing system. PROACTIVELY activate for: (1) Dynamic routes with [slug], (2) Catch-all routes [...slug], (3) Route groups for organization, (4) Parallel routes with @slot, (5) Intercepting routes for modals, (6) Private folders with _prefix, (7) Route Handlers (API), (8) Search params handling, (9) Programmatic navigation. Provides: Dynamic routing patterns, parallel route slots, modal interception, API handlers. Ensures flexible routing with proper URL structure.
npx skillsauth add JosiahSiegel/claude-plugin-marketplace nextjs-routing-advancedInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Route Type | Folder Pattern | URL Example |
|------------|----------------|-------------|
| Dynamic | [slug] | /blog/hello → { slug: 'hello' } |
| Catch-all | [...slug] | /docs/a/b → { slug: ['a', 'b'] } |
| Optional catch-all | [[...slug]] | /shop or /shop/a/b |
| Route group | (name) | No URL impact, layout grouping |
| Parallel route | @slot | Independent loading/error |
| Intercept same level | (.)path | Modal pattern |
| Private folder | _folder | Not a route |
| Navigation | Code | Use Case |
|------------|------|----------|
| Link | <Link href="/path"> | Declarative nav |
| router.push | router.push('/path') | Programmatic nav |
| router.replace | router.replace('/path') | No history entry |
| redirect | redirect('/path') | Server redirect |
| Route Handler | Method | Pattern |
|---------------|--------|---------|
| GET | Read | export async function GET() {} |
| POST | Create | export async function POST() {} |
| PUT | Update | export async function PUT() {} |
| DELETE | Delete | export async function DELETE() {} |
Use for advanced routing patterns:
Related skills:
nextjs-app-routernextjs-middlewarenextjs-data-fetching// app/blog/[slug]/page.tsx
interface PageProps {
params: Promise<{ slug: string }>;
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
// /blog/hello-world → { slug: 'hello-world' }
// app/shop/[category]/[product]/page.tsx
interface PageProps {
params: Promise<{ category: string; product: string }>;
}
export default async function ProductPage({ params }: PageProps) {
const { category, product } = await params;
const productData = await getProduct(category, product);
return <div>{productData.name}</div>;
}
// /shop/electronics/laptop → { category: 'electronics', product: 'laptop' }
// app/docs/[...slug]/page.tsx
interface PageProps {
params: Promise<{ slug: string[] }>;
}
export default async function DocsPage({ params }: PageProps) {
const { slug } = await params;
// slug is an array of path segments
const doc = await getDoc(slug.join('/'));
return <div>{doc.content}</div>;
}
// /docs/getting-started → { slug: ['getting-started'] }
// /docs/api/auth/login → { slug: ['api', 'auth', 'login'] }
// app/shop/[[...slug]]/page.tsx
interface PageProps {
params: Promise<{ slug?: string[] }>;
}
export default async function ShopPage({ params }: PageProps) {
const { slug } = await params;
if (!slug) {
// /shop - show all products
return <AllProducts />;
}
if (slug.length === 1) {
// /shop/category - show category
return <CategoryProducts category={slug[0]} />;
}
// /shop/category/product - show product
return <ProductDetail category={slug[0]} product={slug[1]} />;
}
// /shop → { slug: undefined }
// /shop/electronics → { slug: ['electronics'] }
// /shop/electronics/laptop → { slug: ['electronics', 'laptop'] }
app/
├── (marketing)/
│ ├── layout.tsx # Marketing layout
│ ├── 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 (centered, minimal)
├── login/
│ └── page.tsx # /login
└── register/
└── page.tsx # /register
// app/(marketing)/layout.tsx
export default function MarketingLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<MarketingHeader />
{children}
<MarketingFooter />
</body>
</html>
);
}
// app/(app)/layout.tsx
export default function AppLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<AppSidebar />
<main>{children}</main>
</body>
</html>
);
}
app/
├── layout.tsx
├── page.tsx
├── @analytics/
│ ├── page.tsx
│ └── loading.tsx
├── @team/
│ ├── page.tsx
│ └── loading.tsx
└── @notifications/
└── page.tsx
// app/layout.tsx
export default function Layout({
children,
analytics,
team,
notifications,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
notifications: React.ReactNode;
}) {
return (
<div className="dashboard">
<main>{children}</main>
<aside>
{analytics}
{team}
{notifications}
</aside>
</div>
);
}
// app/layout.tsx
import { auth } from '@/lib/auth';
export default async function Layout({
children,
admin,
user,
}: {
children: React.ReactNode;
admin: React.ReactNode;
user: React.ReactNode;
}) {
const session = await auth();
return (
<div>
{children}
{session?.role === 'admin' ? admin : user}
</div>
);
}
// app/@analytics/default.tsx
// Shown when the slot doesn't match current route
export default function AnalyticsDefault() {
return null; // or a default UI
}
app/
├── feed/
│ └── page.tsx # /feed - main feed
├── photo/
│ └── [id]/
│ └── page.tsx # /photo/123 - full page photo
└── @modal/
└── (.)photo/
└── [id]/
└── page.tsx # Intercepted: shows modal
(.) - Match same level
(..) - Match one level above
(..)(..) - Match two levels above
(...) - Match from root
// app/feed/page.tsx
import Link from 'next/link';
export default function FeedPage() {
const photos = await getPhotos();
return (
<div className="grid">
{photos.map((photo) => (
<Link key={photo.id} href={`/photo/${photo.id}`}>
<img src={photo.thumbnail} alt={photo.title} />
</Link>
))}
</div>
);
}
// app/@modal/(.)photo/[id]/page.tsx
import { Modal } from '@/components/modal';
export default async function PhotoModal({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<Modal>
<img src={photo.url} alt={photo.title} />
<p>{photo.description}</p>
</Modal>
);
}
// app/photo/[id]/page.tsx - Full page view (direct navigation)
export default async function PhotoPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<div className="photo-page">
<img src={photo.url} alt={photo.title} />
<h1>{photo.title}</h1>
<p>{photo.description}</p>
</div>
);
}
// app/layout.tsx
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
);
}
// components/modal.tsx
'use client';
import { useRouter } from 'next/navigation';
export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter();
return (
<div className="modal-overlay" onClick={() => router.back()}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button onClick={() => router.back()}>Close</button>
{children}
</div>
</div>
);
}
app/
├── _components/ # Private - not a route
│ ├── Button.tsx
│ └── Card.tsx
├── _lib/ # Private - not a route
│ └── utils.ts
├── dashboard/
│ ├── _components/ # Private - scoped to dashboard
│ │ └── Chart.tsx
│ └── page.tsx
└── page.tsx
// app/api/posts/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
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 });
}
export async function PUT(request: Request) {
const body = await request.json();
const post = await updatePost(body);
return NextResponse.json(post);
}
export async function DELETE(request: Request) {
await deletePost();
return new NextResponse(null, { status: 204 });
}
// app/api/posts/[id]/route.ts
interface RouteContext {
params: Promise<{ id: string }>;
}
export async function GET(request: Request, context: RouteContext) {
const { id } = await context.params;
const post = await getPost(id);
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json(post);
}
// Force dynamic
export const dynamic = 'force-dynamic';
// Set runtime
export const runtime = 'edge';
// Set revalidation
export const revalidate = 60;
// app/search/page.tsx
interface SearchPageProps {
searchParams: Promise<{ q?: string; page?: string; sort?: string }>;
}
export default async function SearchPage({ searchParams }: SearchPageProps) {
const { q, page = '1', sort = 'relevance' } = await searchParams;
const results = await search({
query: q,
page: parseInt(page),
sort,
});
return (
<div>
<h1>Results for: {q}</h1>
<SearchResults results={results} />
</div>
);
}
'use client';
import { useRouter, useSearchParams, usePathname } from 'next/navigation';
export function SearchFilter() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const updateSearch = (key: string, value: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set(key, value);
router.push(`${pathname}?${params.toString()}`);
};
return (
<select onChange={(e) => updateSearch('sort', e.target.value)}>
<option value="relevance">Relevance</option>
<option value="date">Date</option>
<option value="price">Price</option>
</select>
);
}
'use client';
import { useRouter } from 'next/navigation';
export function NavigationExample() {
const router = useRouter();
return (
<div>
<button onClick={() => router.push('/dashboard')}>
Go to Dashboard
</button>
<button onClick={() => router.replace('/login')}>
Replace with Login
</button>
<button onClick={() => router.back()}>
Go Back
</button>
<button onClick={() => router.forward()}>
Go Forward
</button>
<button onClick={() => router.refresh()}>
Refresh
</button>
<button onClick={() => router.prefetch('/about')}>
Prefetch About
</button>
</div>
);
}
// In Server Component or Server Action
import { redirect } from 'next/navigation';
export default async function ProtectedPage() {
const session = await getSession();
if (!session) {
redirect('/login');
}
return <div>Protected content</div>;
}
import { permanentRedirect } from 'next/navigation';
export default async function OldPage() {
permanentRedirect('/new-page'); // 308 status
}
| Practice | Description | |----------|-------------| | Use route groups for organization | Group by feature or layout | | Implement loading states | Add loading.tsx for each segment | | Use parallel routes for dashboards | Independent loading/error states | | Intercept for modals | Better UX for overlays | | Keep private folders organized | Use _ prefix for non-routes | | Type your params | Use Promise<> for params and searchParams |
development
Use for Clerk sessions, tokens, webhooks, orgs, and security. PROACTIVELY activate for session tokens, JWT templates, getToken(), custom claims, pending sessions, multi-session UX, organizations, roles, permissions, system vs custom permissions, features/plans, MFA/passkeys/password policy/bot protection, Clerk webhooks, Svix signatures, verifyWebhook(), user/org sync, retries/replays, environment variables, custom domains, secret rotation, logs, and auth security reviews. Provides token semantics, webhook idempotency, authorization defaults, and hardening checklist.
tools
Use for Clerk in Next.js. PROACTIVELY activate for @clerk/nextjs setup, App Router auth()/currentUser(), clerkMiddleware(), proxy.ts/middleware.ts, createRouteMatcher(), protected pages/layouts/Route Handlers/Server Actions/API routes/tRPC, auth.protect() role/permission/token checks, ClerkProvider placement, server-only clerkClient, Link prefetch, redirects, 401/404 auth failures, custom domains, __clerk proxy paths, and deployment gotchas. Provides file patterns, server/client boundary rules, matcher templates, and production checks.
development
Use for Clerk frontend auth flows. PROACTIVELY activate for React, JavaScript, Vue, Nuxt, Astro, Expo, React Router, TanStack React Start, or SPA setup; ClerkProvider and publishable-key wiring; SignIn/SignUp/UserButton/UserProfile/OrganizationSwitcher; custom useUser/useAuth/useClerk/useSignIn/useSignUp/useSession/useOrganization flows; multi-session UX; cross-origin getToken() fetches; loading states, redirects, routing, CORS/cookies, or hydration bugs. Provides SDK selection, UI patterns, token-fetch templates, and frontend gotchas.
development
Use for Clerk dev/prod readiness, deployment, and multi-language implementation planning. PROACTIVELY activate for environment variables, pk_test/sk_test vs pk_live/sk_live, local dev, preview/staging/prod instances, domains/DNS, redirects, OAuth credentials, custom domains/proxy, authorizedParties, CSP, CORS/cookies, webhooks/tunnels, Vercel/Netlify/Cloudflare/API gateways, monitoring/troubleshooting, and backends in Node/Express/Fastify, Python/FastAPI/Django/Flask, Go, Ruby/Rails, Java/Spring, .NET, PHP/Laravel. Provides checklists, rollout plans, and language-portable patterns.