"Build Next.js apps with the App Router: server vs client components, routes and layouts, route handlers, server actions, data fetching and caching, metadata/SEO, images, env vars and deploying. Use when the user asks for a Next.js app or site, or works in a project with next in package.json."
Instal·la
npx skillscat add v-groups-foundations/freetoken/nextjs-app Instal·la des del registre de SkillsCat.
Next.js app (App Router)
Next.js changes between major versions (caching defaults, async params, config names). Before relying
on an API, check the installed version (npm ls next) and read its bundled docs innode_modules/next/dist/docs/ if present (search them with grep), otherwise the version's docs on
nextjs.org. Follow the existing project's conventions over this file.
1. Scaffold (PowerShell)
npx create-next-app@latest my-app --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
cd my-appStart with run_app: npm run dev (http://localhost:3000).
2. Routes and files (src/app/)
page.tsxmakes a route; folders are URL segments:app/blog/[slug]/page.tsx->/blog/hello.layout.tsxwraps children and persists across navigation; the root layout has<html>and<body>.loading.tsx(Suspense fallback),error.tsx(must be'use client'),not-found.tsx.- Route groups
(marketing)/organize without changing URLs. Private folders_components/. route.ts= API endpoint (route handler): exportGET,POST, etc.- In Next 15+,
paramsandsearchParamsare Promises:const { slug } = await params;.
3. Server vs client components
- Default is a Server Component: can be
async, fetch data, read env secrets and the DB, sends no JS. - Add
'use client'at the top only for interactivity: state, effects, event handlers, browser APIs. - Push
'use client'down to the smallest leaf (a button, a form), keep pages and layouts server. - Pass only serializable props (no functions, class instances) from server to client components.
- Never import server-only code (DB, secrets) into a client component; add
import 'server-only'
to such modules to make mistakes fail at build.
4. Data fetching and mutations
// app/posts/page.tsx (Server Component)
export default async function Posts() {
const posts = await db.post.findMany({ orderBy: { createdAt: 'desc' } });
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}- Fetch in the server component that needs the data; run independent fetches with
Promise.all. - Caching differs by version: check whether
fetchis cached by default in the installed version, and
be explicit:fetch(url, { cache: 'no-store' })for live data,{ next: { revalidate: 60 } }for ISR,export const dynamic = 'force-dynamic'when a page must always render per request. - Mutations: Server Actions (
'use server'functions) called from<form action={fn}>; validate input
with zod inside the action, check auth inside the action, thenrevalidatePath('/posts'). - Route handlers (
route.ts) for webhooks, external clients and non-form APIs.
5. Metadata and SEO
export const metadata = { title: { default: 'Acme', template: '%s | Acme' },
description: 'Invoices in 30 seconds.', openGraph: { images: ['/og.png'] } };
export async function generateMetadata({ params }) { const { slug } = await params; /* ... */ }Add app/sitemap.ts and app/robots.ts; set metadataBase in the root layout.
6. Images, fonts, links
next/imagewithwidth/height(orfill+ sized parent) andpriorityfor the LCP image.
Remote images needimages.remotePatternsinnext.config.next/font/google(import { Inter } from 'next/font/google') self-hosts fonts with no layout shift.<Link href>for internal navigation.
7. Env vars
.env.local (gitignored). Server-only by default; only NEXT_PUBLIC_* reach the browser. Secrets
never get the NEXT_PUBLIC_ prefix. Restart the dev server after changing env files.
8. Build and deploy
npm run build # type-check, lint, prerender; must pass
npm run start # production server on :3000Vercel: push to GitHub and import the repo, or npx vercel / npx vercel --prod; set env vars in the
project settings. Elsewhere: output: 'standalone' in next.config for Docker (see docker-deploy).
Check before you finish
npm run buildpasses (quote the summary); fix type and lint errors.- run_app
npm run devand inspect_preview key routes: no hydration warnings or console errors. - View page source of a public page: content and
<title>/meta are in the HTML. - Mutations update the UI after submit (revalidation works).
Common mistakes
'use client'on pages/layouts, shipping everything to the browser.- Hydration mismatches from
Date.now(),Math.random()orwindowduring render. - Fetching your own route handlers from server components (call the function/DB directly).
- Assuming caching behavior from a different Next version.
- Using
pages/router APIs (getServerSideProps) insideapp/.