跪拜 Guibai
← All articles
Frontend · Full-Stack · Next.js

One Project, Frontend and Backend: A Next.js App Router Todo Walkthrough

By YIAN ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A React developer who has only worked with Vite SPAs and separate Node backends can absorb the full-stack shift in a single sitting — filesystem routing, server components, and co-located API handlers remove entire categories of glue code and deployment overhead.

Summary

App Router replaces manual route config with folder conventions: a `page.tsx` becomes a route, a `route.ts` inside `api/` becomes a backend endpoint. Components default to server-rendered for SEO and fast first paint; interactive pieces opt into the browser with `'use client'`. A complete Todo example wires a client-side page to in-memory CRUD handlers, surfacing the exact caching and request-header pitfalls that break the loop for newcomers.

The walkthrough covers the two rendering modes (CSR vs. SSR), the three-layer SEO model (metadata, body content, rendering strategy), and the mechanics of `force-dynamic`, `cache: "no-store"`, and `Content-Type` headers. It also contrasts two state-update strategies — optimistic local append versus a full re-fetch — and explains when each one silently fails.

Production readiness is flagged honestly: the in-memory array is a demo stand-in; real persistence demands Prisma + PostgreSQL. The closing roadmap points to Server Actions, dynamic routes, revalidation, and middleware as the natural next steps.

Takeaways
Folders define routes: `app/about/page.tsx` maps to `/about`; `app/api/todos/route.ts` maps to `GET/POST /api/todos`.
Components are server-rendered by default. Add `'use client'` only when the component uses hooks, event handlers, or browser APIs.
A `'use client'` component still gets its static HTML rendered on the server; only the interactive logic runs in the browser.
Export `dynamic = "force-dynamic"` in a route handler to prevent Next.js from caching stale API responses.
Native `fetch` to a route handler requires `cache: "no-store"` on GETs and `Content-Type: application/json` on POSTs, or data silently fails to update or parse.
After a POST, appending the returned object to local state avoids an extra GET and sidesteps cache issues; re-fetching the full list is safer only when both `force-dynamic` and `cache: "no-store"` are set.
The in-memory array used in the demo evaporates on server restart; production needs a real database such as PostgreSQL with Prisma.
Conclusions

The three most common beginner bugs — stale GETs after mutation, missing `Content-Type` headers, and rendering raw objects instead of strings — are all consequences of the same gap: developers treat the integrated frontend/backend as magic and skip the HTTP contract details that a separate backend would have forced them to learn.

Next.js blurs the line between frontend and backend so thoroughly that the caching layer becomes the new boundary where developers get stuck; `force-dynamic` and `cache: "no-store"` are not performance knobs but correctness requirements in a read-after-write flow.

The hybrid rendering model for `'use client'` components — static shell from the server, interactivity hydrated in the browser — is under-explained in most tutorials, yet it is the mechanism that lets a single component satisfy both SEO and UX.

Concepts & terms
App Router
Next.js 13+ routing system where folders under `app/` become URL paths, `page.tsx` files serve as route entry points, and `layout.tsx` files define shared UI shells for nested routes.
Server Component (RSC)
A React component that renders entirely on the server, sending zero JavaScript to the browser. It is the default in App Router and is suited for static content.
Route Handler
A file at `app/api/*/route.ts` that exports functions named after HTTP methods (GET, POST, etc.), turning a Next.js project into its own API server without a separate backend process.
force-dynamic
A route segment config option that disables static caching for an API route, forcing the handler to re-execute on every request so mutations become visible to subsequent reads.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗