ai next tutorial 2026-08-30
write a tutorial of next js
ai answer
Next.js Tutorial (App Router)
This is a practical introduction to Next.js 16 (current stable: 16.3.3). You will learn how to create an app, route pages, mix Server and Client Components, fetch data, handle forms, and deploy.
Official docs: https://nextjs.org/docs and https://nextjs.org/learn .
1. What Next.js is
Next.js is a React framework for full-stack web apps. On top of React it gives you:
- File-system routing (folders become URLs)
- Server Components by default (less JavaScript in the browser)
- Streaming / Suspense for faster first paint
- Built-in image, font, and metadata optimization
- Route Handlers and Server Actions so you can write backend logic in the same project
- Turbopack as the default bundler for
next dev
There are two routers:
| Router | Status |
|---|---|
App Router (app/) |
Recommended. Server Components, layouts, streaming |
Pages Router (pages/) |
Still supported for older apps |
This tutorial uses the App Router only.
2. Prerequisites
- Node.js 20.9+
- Comfort with HTML, CSS, JavaScript, and React (components, props, hooks)
- A terminal and a code editor (VS Code is fine)
Check Node:
node -v
3. Create a project
Recommended (pnpm, npm, or yarn all work):
npx create-next-app@latest my-app
Or skip prompts:
pnpm create next-app my-app --yes cd my-app pnpm dev
Open http://localhost:3000.
Recommended defaults today: TypeScript, ESLint, Tailwind CSS, App Router, import alias @/*.
Useful scripts:
{ "dev": "next dev", "build": "next build", "start": "next start" }
Turbopack is the default for development. Use next dev --webpack only if you need Webpack.
4. Project structure
A typical new app looks like this:
my-app/ ├── app/ │ ├── layout.tsx # root layout (required) │ ├── page.tsx # home page → / │ ├── globals.css │ └── favicon.ico ├── public/ # static files → /filename ├── next.config.ts ├── package.json └── tsconfig.json
Special files inside app/:
| File | Role |
|---|---|
page.tsx |
Makes the folder a public route |
layout.tsx |
Shared UI that wraps pages |
loading.tsx |
Instant loading UI (Suspense) |
error.tsx |
Error boundary for that segment |
not-found.tsx |
Custom 404 |
route.ts |
API / Route Handler |
template.tsx |
Like a layout, but remounts on navigation |
A folder is not a URL unless it contains page.tsx or route.ts. You can colocate components next to pages safely.
5. Pages and layouts
A page
app/page.tsx is /:
export default function Page() { return <h1>Home</h1> }
Nested pages
app/about/page.tsx → /about app/blog/page.tsx → /blog app/blog/[slug]/page.tsx → /blog/hello-world
Root layout (required)
Every app needs a root layout with <html> and <body>:
// app/layout.tsx import type { Metadata } from 'next' import './globals.css' export const metadata: Metadata = { title: 'My App', description: 'A Next.js tutorial app', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <header> <nav>…</nav> </header> <main>{children}</main> </body> </html> ) }
Layouts do not remount on navigation. State in a shared header survives route changes.
Nested layout
// app/blog/layout.tsx export default function BlogLayout({ children, }: { children: React.ReactNode }) { return ( <section> <aside>Blog sidebar</aside> {children} </section> ) }
/blog and /blog/[slug] both wrap in this layout and the root layout.
6. Routing conventions
app/blog/[slug]/page.tsx # /blog/my-post app/shop/[...slug]/page.tsx # /shop/a, /shop/a/b app/docs/[[...slug]]/page.tsx # /docs and /docs/a/b app/(marketing)/about/page.tsx # /about (group does not appear in URL) app/blog/_components/Card.tsx # private folder, not a route
Dynamic params are Promises — await them:
// app/blog/[slug]/page.tsx export default async function PostPage({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params return <h1>Post: {slug}</h1> }
searchParams works the same way (Promise<{ q?: string }>).
7. Linking and navigation
Use <Link> for internal routes. Next.js prefetches linked pages in the viewport.
import Link from 'next/link' export default function Nav() { return ( <nav> <Link href="/">Home</Link> <Link href="/about">About</Link> <Link href="/blog/hello">Hello post</Link> </nav> ) }
Client-side navigation (after an event):
'use client' import { useRouter } from 'next/navigation' export function BackButton() { const router = useRouter() return <button onClick={() => router.push('/blog')}>Back</button> }
8. Server vs Client Components
Default: Server Components. They run on the server, can await data and use secrets, and ship no extra client JS for themselves.
Use a Client Component when you need:
useState/useEffect/ other hooks- click / input handlers
- browser APIs (
window,localStorage)
Mark the entry file with 'use client' at the very top:
// app/components/counter.tsx 'use client' import { useState } from 'react' export default function Counter() { const [count, setCount] = useState(0) return ( <button onClick={() => setCount((c) => c + 1)}> Clicked {count} times </button> ) }
Compose them: fetch on the server, pass props into a small client island.
// app/blog/[slug]/page.tsx (Server Component) import LikeButton from '@/app/components/like-button' import { getPost } from '@/lib/posts' export default async function Page({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await getPost(slug) return ( <article> <h1>{post.title}</h1> <p>{post.body}</p> <LikeButton initialLikes={post.likes} /> </article> ) }
Rule of thumb: keep 'use client' as low in the tree as possible.
9. Fetching data
In a Server Component you can async/await directly:
export default async function BlogPage() { const res = await fetch('https://api.vercel.app/blog') const posts = await res.json() return ( <ul> {posts.map((post: { id: string; title: string }) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }
Database access is also fine here — credentials stay on the server.
Parallel fetches (faster):
const [user, posts] = await Promise.all([getUser(), getPosts()])
Share a fetch within one request with cache():
import { cache } from 'react' export const getUser = cache(async () => { const res = await fetch('https://api.example.com/user') return res.json() })
Streaming
Show the shell immediately, stream the slow part:
import { Suspense } from 'react' export default function Page() { return ( <> <h1>Blog</h1> <Suspense fallback={<p>Loading posts…</p>}> <PostList /> </Suspense> </> ) }
Or add app/blog/loading.tsx for a segment-wide skeleton.
10. Server Actions (mutations)
Server Actions let forms talk to the server without writing a separate API route.
// app/actions.ts 'use server' import { redirect } from 'next/navigation' import { revalidatePath } from 'next/cache' export async function createPost(formData: FormData) { const title = formData.get('title') as string // save to DB… revalidatePath('/blog') redirect('/blog') }
// app/blog/new/page.tsx import { createPost } from '@/app/actions' export default function NewPostPage() { return ( <form action={createPost}> <input name="title" required /> <button type="submit">Publish</button> </form> ) }
redirect() after a successful mutation; revalidatePath() / revalidateTag() to refresh cached UI.
11. Route Handlers (APIs)
app/api/hello/route.ts → GET /api/hello
export async function GET() { return Response.json({ message: 'Hello from Next.js' }) } export async function POST(request: Request) { const body = await request.json() return Response.json({ received: body }, { status: 201 }) }
Use Route Handlers for webhooks, public JSON APIs, and non-form clients. Prefer Server Actions for UI forms in the same app.
12. Images, fonts, metadata
Images
import Image from 'next/image' <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
Remote images need images.remotePatterns in next.config.ts.
Fonts
import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'] }) export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" className={inter.className}> <body>{children}</body> </html> ) }
SEO metadata
import type { Metadata } from 'next' export const metadata: Metadata = { title: { default: 'My Blog', template: '%s | My Blog', }, description: 'Notes on Next.js', }
Per-page (dynamic):
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 } }
13. Styling
New apps include Tailwind CSS. Utility classes work immediately:
<h1 className="text-3xl font-bold tracking-tight">Hello</h1>
You can also use CSS Modules (Button.module.css) or global CSS from app/globals.css.
14. Errors and not-found
// app/blog/error.tsx 'use client' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return ( <div> <p>Something went wrong.</p> <button onClick={reset}>Try again</button> </div> ) }
// app/blog/[slug]/page.tsx import { notFound } from 'next/navigation' export default async function Page({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await getPost(slug) if (!post) notFound() return <h1>{post.title}</h1> }
Add app/not-found.tsx for a custom 404 page.
15. Environment variables
# .env.local DATABASE_URL=postgres://… NEXT_PUBLIC_SITE_URL=http://localhost:3000
- No prefix → server only (safe for secrets)
NEXT_PUBLIC_→ exposed to the browser
Never put API secrets in Client Components.
16. Build and deploy
pnpm build # production build pnpm start # run the production server
Vercel is the path of least resistance: push to GitHub, import the repo, done. Next.js also runs on Node hosts, Docker, and platforms that support the Build Adapters API.
Before production: use Server Components by default, <Link> for navigation, custom error.tsx / not-found.tsx, and keep client JS small.
Stay on a patched release (as of late August 2026 that is 16.3.3).
Mini project: put it together
- Create the app with
create-next-app. - Add
app/blog/page.tsx— list posts fromfetch. - Add
app/blog/[slug]/page.tsx— awaitparams, fetch one post, callnotFound()if missing. - Add
app/blog/layout.tsx— shared blog chrome. - Add a Client
LikeButton. - Add
app/blog/new/page.tsx+ a Server Action to create a post. - Add
generateMetadataon the post page. - Deploy.
Common mistakes
- Forgetting
'use client'on interactive components (or putting it on the whole layout) - Using
next/router(Pages Router) instead ofnext/navigation - Treating
params/searchParamsas plain objects instead of awaiting the Promise - Fetching in Client Components when a Server Component would do
- Putting secrets in
NEXT_PUBLIC_variables - Using
<a href>for internal routes instead of<Link>
Where to go next
- https://nextjs.org/docs/app/getting-started/layouts-and-pages
- https://nextjs.org/docs/app/getting-started/server-and-client-components
- https://nextjs.org/learn
- https://nextjs.org/docs/app/guides
If you want a follow-up, say which direction you prefer: a blog with a database, auth, or a dashboard with Server Actions.