Loading...
Loading...
Implement Next.js App Router patterns including Server Components, Client Components, layouts, route organization, and data fetching. Use when building with App Router, organizing routes, or implementing modern Next.js patterns. Trigger words include "App Router", "Server Component", "Client Component", "layout", "app directory".
npx skill4agent add armanzeroeight/fastagent-plugins app-router-helperapp/app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page (/)
├── loading.tsx # Loading UI
├── error.tsx # Error UI
├── not-found.tsx # 404 page
└── about/
└── page.tsx # About page (/about)layout.tsxpage.tsxloading.tsxerror.tsxtemplate.tsxroute.ts// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Header />
{children}
<Footer />
</body>
</html>
);
}// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<Sidebar />
<main>{children}</main>
</div>
);
}// app/products/page.tsx
// No 'use client' = Server Component
async function ProductsPage() {
// Can fetch data directly
const products = await db.products.findMany();
return (
<div>
{products.map(p => (
<ProductCard key={p.id} product={p} />
))}
</div>
);
}
export default ProductsPage;// app/components/AddToCart.tsx
'use client';
import { useState } from 'react';
export function AddToCart({ productId }: { productId: string }) {
const [count, setCount] = useState(1);
const handleAdd = () => {
// Client-side logic
addToCart(productId, count);
};
return (
<div>
<button onClick={() => setCount(count - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={handleAdd}>Add to Cart</button>
</div>
);
}// app/posts/page.tsx
async function PostsPage() {
// Fetch in Server Component
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // Cache for 1 hour
}).then(res => res.json());
return <PostList posts={posts} />;
}async function Page() {
// Fetch in parallel
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts(),
]);
return (
<div>
<UserProfile user={user} />
<PostList posts={posts} />
</div>
);
}async function Page() {
const user = await fetchUser();
const posts = await fetchUserPosts(user.id); // Depends on user
return <div>...</div>;
}app/
├── (marketing)/
│ ├── layout.tsx # Marketing layout
│ ├── about/
│ │ └── page.tsx # /about
│ └── contact/
│ └── page.tsx # /contact
└── (shop)/
├── layout.tsx # Shop layout
└── products/
└── page.tsx # /productsapp/
└── products/
└── [id]/
└── page.tsx # /products/123// app/products/[id]/page.tsx
export default function ProductPage({
params,
}: {
params: { id: string }
}) {
return <div>Product {params.id}</div>;
}app/
└── docs/
└── [...slug]/
└── page.tsx # /docs/a, /docs/a/b, /docs/a/b/c// app/dashboard/loading.tsx
export default function Loading() {
return <div>Loading dashboard...</div>;
}// app/dashboard/error.tsx
'use client'; // Error components must be Client Components
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}// app/not-found.tsx
export default function NotFound() {
return <div>404 - Page Not Found</div>;
}import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
<FastComponent />
</div>
);
}app/
└── dashboard/
├── layout.tsx
├── @analytics/
│ └── page.tsx
├── @team/
│ └── page.tsx
└── page.tsx// app/dashboard/layout.tsx
export default function Layout({
children,
analytics,
team,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<div>
{children}
<div className="grid">
{analytics}
{team}
</div>
</div>
);
}app/
└── photos/
├── [id]/
│ └── page.tsx
└── (.)[id]/
└── page.tsx # Intercepts /photos/[id]// app/products/[id]/page.tsx
import { Metadata } from 'next';
export async function generateMetadata({
params,
}: {
params: { id: string }
}): Promise<Metadata> {
const product = await fetchProduct(params.id);
return {
title: product.name,
description: product.description,
openGraph: {
images: [product.image],
},
};
}// app/api/products/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const products = await db.products.findMany();
return NextResponse.json(products);
}
export async function POST(request: Request) {
const body = await request.json();
const product = await db.products.create({ data: body });
return NextResponse.json(product, { status: 201 });
}// app/api/products/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const product = await db.products.findUnique({
where: { id: params.id }
});
return NextResponse.json(product);
}