A
AiTechWorlds
AiTechWorlds

The App Router lives in the /app directory. Key file conventions:
| File | Purpose |
|---|---|
page.tsx | UI for a route segment |
layout.tsx | Shared UI for segment + children |
loading.tsx | Streaming loading UI |
error.tsx | Error boundary UI |
not found.tsx | 404 page |
route.ts | API endpoint handler |
template.tsx | Like layout but re renders on navigate |
app/
page.tsx โ /
about/page.tsx โ /about
blog/
page.tsx โ /blog
[slug]/page.tsx โ /blog/:slug
shop/
[...all]/page.tsx โ /shop/a, /shop/a/b (catch-all)
[[...all]]/page.tsx โ /shop, /shop/a, /shop/a/b (optional)
(auth)/ โ Route group (not in URL)
login/page.tsx โ /login
register/page.tsx โ /register// Server Component (default โ no 'use client')
// Can access databases, files, environment variables
// Zero bundle size impact
// Cannot use useState, useEffect, browser APIs
async function ServerPage() {
const data = await fetch("https://api.example.com/data");
const json = await data.json();
return <div>{json.title}</div>;
}
// Client Component โ add 'use client' at top
'use client';
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}Rule: Server components are default. Add 'use client' only when you need interactivity or browser APIs.
// In Server Components โ fetch with auto deduplication
async function Page() {
// Cached by default
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return <PostList posts={posts} />;
}
// No caching (always fresh)
const res = await fetch(url, { cache: 'no-store' });
// ISR โ revalidate every 60 seconds
const res = await fetch(url, { next: { revalidate: 60 } });
// Tag-based revalidation
const res = await fetch(url, { next: { tags: ['posts'] } });
// Direct database query (no fetch needed)
import { db } from '@/lib/db';
const posts = await db.posts.findMany();// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const id = searchParams.get('id');
const users = await db.users.findMany();
return NextResponse.json({ users });
}
export async function POST(req: NextRequest) {
const body = await req.json();
const user = await db.users.create({ data: body });
return NextResponse.json({ user }, { status: 201 });
}
// app/api/users/[id]/route.ts
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const user = await db.users.findUnique({ where: { id } });
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
return NextResponse.json({ user });
}import type { Metadata } from 'next';
// Static metadata
export const metadata: Metadata = {
title: 'My Site',
description: 'Welcome to my site',
};
// Dynamic metadata
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
images: [post.image],
},
};
}// Pre-generate static pages at build time
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}// app/blog/loading.tsx โ shown during page load
export default function Loading() {
return (
<div className="animate-pulse space-y-4">
{[1,2,3].map(i => (
<div key={i} className="h-24 bg-gray-200 rounded-lg" />
))}
</div>
);
}
// app/blog/error.tsx โ error boundary
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}// actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const post = await db.posts.create({ data: { title } });
revalidatePath('/blog');
redirect(`/blog/${post.slug}`);
}
// Use in form
<form action={createPost}>
<input name="title" type="text" />
<button type="submit">Create</button>
</form>// middleware.ts (root level)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const isAuthenticated = request.cookies.has('session');
if (!isAuthenticated && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};params in page/layout/route is now a Promise always await paramscookies() and headers() are now async await cookies()fetch is NOT cached by default (opt in with revalidate)Get more notes like this daily on Telegram!
Free study notes, cheat sheets & AI tips
Last reviewed on June 13, 2026 by the AiTechWorlds Notes Team. Free cheat sheet no signup required.
Join AiTechWorlds on Telegram and get daily AI tips, prompt engineering templates, coding resources, and exclusive content 100% free!
No spam. Leave anytime.