Next.js Is the AI-Friendly Full-Stack Framework, Explained Through a Blog Build
Starting from a "House": Why Next.js is a Full-Stack Framework for AI (with a Blog Project Breakdown)
This article will take you from the most fundamental question — "What problems do JS and React actually solve?" — step by step to "Why choose Next.js?", and finally tie the knowledge together with a real blog project. Plain language throughout, no jargon.
Zero: First, Answer a "Stupid Question": What is a Framework?
Imagine building a house. You can't start by firing bricks every time you build a house — that's too slow. You need a basic architecture that already provides the foundation, walls, and roof, so you only need to focus on assembly and decoration.
This is a Framework.
Without a framework: With a framework:
┌───────────┐ ┌───────────────┐
│ Fire bricks → Build walls →│ │ Foundation (already laid) │
│ Install windows → Lay floor →│ │ Walls (already built) │
│ Decorate... │ │ Roof (already built) │
│ Start from 0 each time│ │ You only handle decoration │
└───────────┘ └───────────────┘
Previously, frameworks only served developers; now, AI can also use frameworks. One could even say that the value of frameworks is amplified in the AI era: frameworks give AI a set of constraints and a context, allowing AI to develop projects more efficiently based on these constraints.
One: The Bottom Layer: What Problems JS and React Solve
There are only three sentences in the notes, but each is worth expanding on:
Functions that return JSX, reactive state. They liberate developers from low-level frontend API imperative pipeline programming, through modern frontend libraries like React/Vue MVVM, allowing you to write business logic directly.
1.1 Imperative vs. Declarative: How We Used to Write Pages
Old-school frontend (vanilla JS / jQuery) was imperative: you had to "tell the browser what to do step by step."
// Imperative: every step is a manual instruction
const btn = document.querySelector("#btn");
const countEl = document.querySelector("#count");
btn.addEventListener("click", () => {
const next = Number(countEl.textContent) + 1;
countEl.textContent = next; // Manually manipulate the DOM: read value, calculate value, write back
});
The code is long and fragile — you have to stare at every piece of DOM and change it manually, like an assembly line worker. This is what the notes call "low-level frontend API imperative pipeline programming".
React took a different approach — declarative: you only describe "what the interface looks like," and the framework handles the rest of the updates.
1.2 Function Components: A Component is a "Function that Returns JSX"
React's core concept can be summed up in one sentence:
Component = a function that returns JSX.
// Function component: a regular function that returns JSX
function Counter() {
return <div>I am part of the interface</div>;
}
1.3 Reactive State: When Data Changes, the Interface Automatically Follows
"Reactive state" means — you just change the data, and the interface updates automatically, without you manually manipulating the DOM.
const [count, setCount] = useState(0); // Declare a "reactive state"
The onClick={setCount(count++)} in the notes is exactly this: click → change data → React automatically renders the new number onto the interface. This is the value of MVVM/reactivity: liberating you from "low-level frontend APIs" to write business logic directly.
Two: Why Use a Framework: Scattered Blocks vs. Pre-made Lego
Original notes:
Without a framework: scattered blocks and tools. Where to put images? /public. Where to put page files? /app. Where to put components? /components. Using a framework: pre-made Lego bricks, providing a series of constraint best practices, which coincidentally align with AI SDD documentation context.
Without a framework, every decision about "where to put things" is yours to make, and different people make different decisions — this is scattered blocks. With a framework, it tells you the standard answer through a set of conventions:
Without a framework → Where to put images? Where to put pages? Where to put components? → All based on gut feeling
With a framework → Images in /public Pages in /app Components in /components → Standard answer
The value of a framework = a set of constraints + best practices:
- Common features are built-in (image optimization, routing, fonts...)
- There are conventions for where files go and where request methods go
- Developers only need to focus on business logic
The key point is here: This set of constraints is exceptionally valuable for AI. As soon as AI sees "this is a Next.js project," it knows what the code should look like and where files should go. That sentence in the notes is very insightful:
AI Context = Components + Reactive Business Logic + Server-Side Rendering + API
For developers, a framework is the "foundation"; for AI, it's the "instruction manual."
Three: Why Specifically Next.js?
3.1 Context Switching Cost: One Less Backend Language to Learn
Traditional full-stack is "Frontend React + Backend Java/Python," two languages, two ecosystems, switching back and forth, with a high context-switching cost.
Traditional Full-stack: React(JS) + Java/Python ← Two languages
Next.js : React(JS) + Next(JS) ← One TS for everything
Next.js is a React-based full-stack framework that integrates server-side rendering, static generation, and APIs, all handled with one set of JS/TS.
3.2 Best Support for AI
AI coding tools like Claude Code / Codex have the best support for Next.js. Because its constraints are clear, CSR/SSR works out of the box, and AI-generated code is less likely to go off the rails.
3.3 Super Rich Ecosystem
① shadcn/ui Component Library
The ecosystem has ElementUI, ANTD... but shadcn/ui is a different kind of thing: it's not "npm install and you have components" , but rather copies the component source code into your project, allowing you to modify it however you want. The component code sits right in components/ui/button.tsx.
For example, in our project, we use shadcn/ui's Button to replace all buttons:
import { Button } from "@/components/ui/button";
// The most basic button
<Button>Confirm</Button>
// Variant + Size
<Button variant="outline" size="sm">Read Full Article →</Button>
// Use the render prop to render it as a <Link>, turning the button into a link
<Button render={<Link href="/blog/what-is-nextjs" />}>Back to Blog List</Button>
A single Button, through variant (default / outline / secondary / ghost / destructive / link) and size, can combine into various styles, managed with tailwindcss + cva (class-variance-authority). This is "vibe coding writing components, importing components": AI writes the component, you import the component, smooth.
② tailwindcss: Atomic Class Names with Inherent Semantics
text-sm, font-medium, bg-zinc-50... each class name does one thing and carries its own semantics. AI's semantic understanding is very strong; seeing a class name basically allows it to guess the styling intent, so tailwind is particularly suitable for AI learning/generation.
③ Vercel Company
Next.js is maintained by Vercel, which is the world's only JS stack AI coding Agent + AI ecosystem technology company. The benefits are very practical: quick publishing, subdomains, domain binding, one-click deployment after development, no need to fiddle with servers.
Four: Practical: File-System Based Routing (Directory is URL)
Original notes:
- File-system based route mapping: page.tsx / layout.tsx shared layout / loading.tsx loading UI / not-found.tsx 404 / error.tsx error UI
- Directory mapping: directory names directly map to URL paths
4.1 Directory Names Directly Become URLs
This is the biggest feature of the Next.js App Router: no need to write any route configuration manually, create a folder + create a page.tsx = a page.
app/
├── page.tsx → / Homepage
├── about/
│ └── page.tsx → /about About page
└── blog/
├── page.tsx → /blog Blog list
└── [slug]/ → /blog/xxx Blog detail (dynamic)
[slug] is a dynamic route segment — the name inside the brackets is the parameter in the URL. When a user visits /blog/what-is-nextjs, the slug is what-is-nextjs.
4.2 Several "Convention Files" Each Have Their Own Role
| File Name | Role |
|---|---|
page.tsx |
Page content |
layout.tsx |
Layout, shared by all child pages (the navbar goes here) |
loading.tsx |
Loading UI |
not-found.tsx |
404 page |
error.tsx |
Error UI |
4.3 Interpreting This Code with "Three Stylistic Requirements" (Key Point)
When generating this project, I proposed three stylistic requirements: ① interface Props ② Function Components ③ TypeScript Type System. Below, I'll use the real code from the blog detail page to explain these three points thoroughly.
① interface / type Props: Constrain Component Parameters with Types
On a dynamic route page, the slug comes from the URL, and Next.js passes the URL parameters in via params. We use a Props type to constrain it:
// ① Define the Props type: params is a Promise containing { slug }
type Props = {
params: Promise<{ slug: string }>;
};
// ② Function component signature: { params }: Props —— parameters are "locked" by the type
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params; // await to get the slug
const post = posts.find((p) => p.slug === slug);
// ...
}
Why write this Props type? Because of type safety — the compiler checks in advance "what the component's required parameters look like," reporting errors immediately if written incorrectly, without waiting for a runtime crash. Also note that params is a Promise, so you must await it inside the component — this is a new convention for dynamic routes in Next.js 15+, forcing you to read parameters asynchronously.
② Function Components: A Component is a "Function that Returns JSX"
Pay attention to the syntax: export default function BlogPostPage(...). No class, no this, just a regular function + props destructuring. This is exactly the "function that returns JSX" mentioned in Chapter One. The previously popular React.FC syntax is outdated; the official recommendation now is to write functions directly with explicit type annotations — this is more AI-friendly because the function signature itself is documentation.
③ TS Type System: Making Data and "Interfaces" Trustworthy
The mock data for the two articles is defined with types:
export type BlogPost = {
slug: string;
title: string;
date: string;
description: string;
content: string[]; // Body text stored as an array of paragraphs
};
Because findPost might not find anything (returning BlogPost | undefined), the component must check for null, calling notFound() if nothing is found. This layer of the type system makes the entire data flow "trustworthy" from start to finish.
4.4 Two Articles: Rendering with slug + mock data
This is the requirement of "clicking two articles, rendering the article using slug mock data."
// posts.ts —— mock data for two articles
export const posts: BlogPost[] = [
{ slug: "what-is-nextjs", title: "What is Next.js?", content: ["...", "..."] },
{ slug: "app-router-vs-pages-router", title: "App Router vs Pages Router", content: ["...", "..."] },
];
- List page (
/blog):posts.map()renders cards, clicking a card navigates to/blog/${post.slug} - Detail page (
/blog/[slug]): finds the post inpostsusing the slug, renders the body if found, returns 404 if not
Plus, with generateStaticParams, these two articles will be pre-generated into static HTML at build time:
export function generateStaticParams() {
return posts.map((post) => ({ slug: post.slug }));
}
User visits /blog/what-is-nextjs → directly hits the pre-built static page, instant open. Flowchart:
/blog List Page (Static) ──click──▶ /blog/[slug] Detail Page
│
slug lookup mock data
┌────┴────┐
Found Not Found
│ │
Render Body notFound() 404
Five: The Link Component: The Secret to No Page Refresh
Original notes:
Link component —— It's client-side navigation, no need to refresh the page. (Frontend routing) Hash, History Router partial refresh still needs to request the backend, just doesn't refresh the whole page (white flash). During frontend navigation, next.js automatically sends an RSC payload, the data is fetched from the backend, just via an Ajax request. Preloads linkable pages to improve speed: the browser downloads the target page data in advance during idle time, "instant open."
5.1 What is Link
<Link> provided by next/link. Using it for page navigation = client-side navigation: clicking it does not refresh the page, no white flash, as smooth as a single-page application.
5.2 The Underlying Truth: It Still Requests the Backend, Just "No Full Page Refresh"
Note that Link does not mean no data is requested, but rather no full page refresh. When clicked, Next.js automatically sends an RSC payload (serialized data of a React Server Component) — the data still comes from the backend, just via an Ajax request, rather than the browser's traditional full-page reload navigation. This is the same principle as frontend routing (Hash / History Router's partial refresh).
Traditional Navigation: Click → Full page request → White flash → Full re-render
Link Navigation: Click → Ajax sends RSC payload → Partial update → No white flash
5.3 prefetch: The Browser "Sneaks Ahead" During Idle Time
Next.js automatically adds prefetching to Links visible within the viewport:
<link rel="prefetch" href="/blog" />
The browser will download the target page's data in advance during idle time, so when you actually click in, the content is already there → "instant open" . This is resource preloading.
Six: Bottom-Layer Easter Egg: What Exactly is DNS?
dns domain system key:value distributed database domain -> ip query (telecom service provider), resolution time
This is a question I asked in class, and I'll dedicate a separate chapter to explain it clearly: "DNS Domain Name System = a key:value distributed database" .
6.1 The Domain Name You Type, the Browser Doesn't Understand
The browser actually connects to the internet using an IP address (a string of numbers). But you type baidu.com. So you need a "translator" to translate the domain name into an IP — this process is called domain name resolution.
6.2 It's Just a key:value Database
A database, after all, stores "key-value pair" records:
key (domain name) value (IP address)
baidu.com → 39.156.66.10
github.com → 140.82.112.3
Written as JSON, it's { "baidu.com": "39.156.66.10" }. This is a key:value database — the key is the domain name, the value is the IP.
6.3 Why is it Called "Distributed"?
Because there isn't just one DNS server in the world. There are thousands of DNS servers globally, deployed in layers, queried nearby, each with its own cache, to prevent a single point of failure:
User's Browser
│ ① "What is the IP for baidu.com?"
▼
Local DNS Server (provided by telecom operators like China Telecom/Unicom)
│ ② No local cache → Query upwards level by level
▼
Root Server ──▶ .com Top-Level Domain Server ──▶ Authoritative DNS Server
│ ③ Found 39.156.66.10, returns along the original path
▼
User gets the IP, starts actually visiting the website
- Distributed = Servers are spread across the globe, providing nearby service + caching, so even if one goes down, others can take over.
- Resolution time = The time taken from "typing the domain name" to "getting the IP." Looking it up fresh every time would be slow, so browsers, operating systems, and telecom operators all cache the resolution results.
6.4 What Does This Have to Do with Web Performance? — dns-prefetch
There's a trick in web performance optimization called dns-prefetch:
<link data-n-head="ssr" rel="dns-prefetch" href="//lf3-short.ibytedapm.com">
Its meaning is: "Resolve the IP for this domain name for me in advance." When you actually need a resource from this domain, you don't have to wait for the resolution temporarily; you connect directly — saving the resolution time. This is the same underlying principle as the prefetch mentioned in the previous chapter: prepare what you'll need in the future, in advance.
Seven: Summary: One Diagram to String the Whole Article Together
Layer ① JS + React Declarative + Reactive, solves the pain of "imperative DOM manipulation"
Layer ② Framework Scattered blocks → Pre-made Lego, constraints + best practices, AI also benefits
Layer ③ Next.js React full-stack + AI-friendly + rich ecosystem
Layer ④ Routing Directory is URL, page/layout/loading/not-found/error
Layer ⑤ Link Client-side navigation without white flash, RSC payload + prefetch for instant open
Layer ⑥ DNS key:value distributed database + dns-prefetch for early resolution
Implementation of the three stylistic requirements:
| Stylistic Requirement | Corresponding Code | Value in One Sentence |
|---|---|---|
| interface Props | type Props = { params: Promise<{slug: string}> } |
Type safety, errors reported immediately if wrong |
| Function Component | export default function BlogPostPage({ params }: Props) |
Component is a function returning JSX, signature is documentation |
| TS Type System | type BlogPost = {...} / generateStaticParams |
Data flow is trustworthy end-to-end, easier for AI to understand |
One sentence to wrap up: What a framework solves isn't "whether you can write code," but "where the code goes and how to write it correctly" — this is true for people, and even more so for AI. This is why Next.js has become the "default full-stack choice" in the AI era.
If you follow along hands-on, the recommended order is: npx create-next-app to create the project → build the /blog list page → add the /blog/[slug] detail page → unify buttons using shadcn/ui's Button → play around with Link's prefetch. Each step corresponds to one of the sections above; after learning, you'll have a real blog that can be deployed.
(Note: The flowcharts in the article are ASCII diagrams, which display normally on Juejin as well; if you want to replace them with more polished graphics, you can draw and upload them yourself based on the diagram structure.)
That's the article. If you need me to: adjust the length (too long/too short), change to a more conversational or more hardcore style, supplement a specific subsection (like the cva principle of shadcn/ui, deeper serialization details of RSC), or turn an ASCII diagram into a text-based step list, just tell me directly.