🙃 我让爬虫终于看到了我的网站,后端同事说"这也行?"(下):App Router 全栈实战

0 阅读7分钟

写在前面:上篇我们把"为什么"讲透了——SPA 体验虽好但 SEO 是短板,SSR 把 React 组件搬到服务器渲染,爬虫就能读到完整内容。这篇进入"怎么做":一条命令创建全栈项目、App Router 约定大于一切、layout.tsx 布局 + metadata 做 SEO、'use client' 声明强交互页面、水合(hydration)激活交互,最后用一个 Todos 全栈 demo 把前后端串起来。上篇讲思想,这篇全是干货,开干!


五、create-next-app:开箱即用的全栈框架

5.1 一条命令,全家桶配齐

npx create-next-app@latest

选择默认配置,一路回车。它已经把全家桶给你配好了:

组件作用
react / react-domReact 界面
typescript类型安全
tailwindcss样式
eslint代码风格规范

老师说:

"next.js 是给react 开发者的开箱即用的利器。"

Next.js 是 React 全栈框架(Nuxt 是 Vue 全栈框架),能写页面(前端),也能写 API(后端)。背靠 Vercel,SEO 做得非常棒,很多 AI 产品用它做官网。

5.2 约定大于一切:App Router

最颠覆认知的一点:

"约定大于一切" "不需要建,文件就是路由,嵌套路由 建立文件夹" "page.tsx 就是页面" "nav 共用的,layout.js 布局文件"

不需要建路由配置文件,文件本身就是路由! 这就是 App Router:

app/
├── layout.tsx          # 根布局:全站共用的导航
├── page.tsx            # 首页:/  → Hello World
├── about/
│   └── page.tsx        # /about → About Us
├── dashboard/
│   ├── layout.tsx      # 嵌套布局:后台自己的导航
│   ├── page.tsx        # /dashboard → Hello Dashboard
│   └── settings/
│       └── page.tsx    # /dashboard/settings → Hello Setting
├── todos/
│   ├── page.tsx        # /todos → 待办事项
│   └── types.ts        # Todo 类型定义
└── api/
    └── todos/
        └── route.ts    # /api/todos → JSON 接口

六、布局与路由:layout.tsx + page.tsx

6.1 根布局与 metadata

渲染规则是这样的:

"先到layout.tsx 布局 - page.tsx"

先渲染布局,再渲染页面。根布局里挂全站导航,用 next/linkLink 做前端跳转:

import type { Metadata } from "next";
import Link from "next/link";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }: LayoutProps<"/">) {
  return (
    <html lang="en" className="h-full antialiased">
      <body className="min-h-full flex flex-col">
        <nav>
          <ul>
            <li><Link href="/">首页</Link></li>
            <li><Link href="/about">关于</Link></li>
            <li><Link href="/dashboard">后台</Link></li>
            <li><Link href="/todos">Todos</Link></li>
          </ul>
        </nav>
        {children}
      </body>
    </html>
  );
}

注意 metadata——上篇讲的 SEO 三件套里的 <title><description>,Next.js 直接让你用 TypeScript 写在组件里。这就是"为了 SEO 而生的框架"。

6.2 嵌套路由与嵌套布局

后台管理系统有自己独立的导航,用嵌套布局:

import Link from 'next/link'

export default function DashboardLayout({children}) {
    return (
        <div>
            <nav>Nav
                <Link href="/dashboard/settings">settings</Link>
            </nav>
            {children}
        </div>
    )
}

/dashboard 的页面先套「后台专用」布局,再套根布局,层层嵌套,像俄罗斯套娃。页面本身朴素得像个招牌:

// 服务器端组件
// 在服务器端 react node 的运行方式
// jsx -> html
export default function Home() {
  return (
    <h1>Hello World</h1>
  );
}

七、服务端组件与客户端组件

7.1 默认都是服务端组件

老师说:

"next.js 将react server component 带到服务器端渲染,ssr开发模式" "jsx -> html seo"

Next.js 把 React Server Component 带到了服务端渲染里:默认情况下组件都是服务端组件,在服务器上编译成 HTML,为了 SEO。

7.2 'use client':强交互页面

那交互怎么办?总不能让用户点按钮就刷新页面吧?

老师说:

"有些页面 强交互" "'use client' 声明" "不是只在浏览器渲染, 先在服务器端把能渲染的渲染完,再去客户端渲染。"

需要强交互的页面,在文件顶部写一行 'use client' 声明即可:

// react 可以在后端运行
'use client';
// 组件在前端渲染的标记
// 添加事件,useEffect,调用后端接口 csr next.js 也支持
// React Server Component

7.3 水合(hydration):水饺理论

老师讲 hydration 的时候打了个极其生动的比方,我直接引用原话:

"包好了水饺,冻上,给你送过来" "水煮 水和" "水合(hydration):浏览器拿到静态 HTML 之后,挂载客户端 js、绑定点击事激活交互;"

服务器先"包好水饺、冻上"(渲染成静态 HTML 发过来),浏览器拿到之后"下水煮"——挂载客户端 JS、绑定点击事件激活交互,这就是水合(hydration)

所以:

"csr 组件 会执行两次,1次载服务器,第二次是在客户端,打补丁,"

客户端组件其实执行两次:第一次在服务器(速冻发货),第二次在客户端(下水煮开、打补丁)。你拿到的不是生面团,是熟的、能直接下锅的饺子。


八、全栈 Demo:Todos

理论讲完,直接上老师的实战 demo:一个全栈待办事项(Todos),类型、API、页面一条龙。

8.1 类型定义:types.ts

export type Todo = {
    id: number;
    content: string;
    completed: boolean;
}

8.2 API 接口:route.ts

Next.js 里除了带 'use client' 的文件都是后端,API 接口也遵守 App Router 约定:

// next.js 除了 use client 都是后端
// api 数据接口 仍然满足 app router 约定
// route.ts 返回json 数据接口的
import { type Todo } from '../../todos/types';

let todos: Todo[] = [
    { id: 1, content: "学习AppRouter", completed: false },
    { id: 2, content: "Next.js 个人官网开发", completed: false },
]
// /api/todos get 请求 restful
export async function GET() {
    // 返回json 数据接口 next.js 封装好了Response
    return Response.json(todos);
}

export async function POST(req:Request) {
    const body = await req.json();
    const newTodo: Todo = {
        id: +Date.now(),
        content: body.content,
        completed: false,
    }
    todos.push(newTodo);
    return Response.json(newTodo);
}

GETPOST 就是两个导出的异步函数,Response.json() 帮你把 JSON 封装好了——RESTful 接口就是这么简单。id: +Date.now() 那个 + 号是把时间戳字符串转成数字,老师的偷懒神器。

8.3 前端页面:page.tsx

'use client' 的交互组件,useState 管状态、useEffect 拉数据、fetch 调接口:

export default function TodosPage() {
    const [todos, setTodos] = useState<Todo[]>([]);
    const [ text, setText ] = useState<string>('');
    const fetchTodos = async() => {
        const res = await fetch("/api/todos");
        const data: Todo[] = await res.json();
        setTodos(data);
    }
    const handleAdd = async() => {
        if (!text.trim()) return
        await fetch("/api/todos", {
            method: "POST",
            headers: {"Content-Type":"application/json"},
            body:JSON.stringify({content:text})
        })
    }
    useEffect(() => {
        fetchTodos();
    }, [])
    return (
        <div style={{marginTop: '12px'}}>
            <h1>待办事项</h1>
            <input value={text} onChange={
                (e) => setText(e.target.value)}
                placeholder="请输入新的待办任务"
            />
            <button onClick={handleAdd} style={{marginLeft:"8px"}}>添加</button>
            <ul style={{paddingLeft: "0", listStyle: "none"}}>
                {
                    todos.map((item) => (
                        <li key={item.id} style={{
                            margin:"8px 0", display:"flex",
                            gap:"10px"
                            }}>
                            <span style={{
                                textDecoration: item.completed ? "line-through" : "none",
                                cursor:"pointer"
                                }}>
                                {item.content}
                            </span>
                            <button>删除</button>
                        </li>
                    ))
                }
            </ul>
        </div>
    )
}

这就是一整套全栈闭环:前端页面 fetch("/api/todos") → Next.js 后端 route.ts 处理 → 返回 JSON → 前端渲染。之前学 React 前后端分离,要开两个项目;现在 Next.js 一个项目全搞定。


九、总结:大前端手里的 Next.js

概念说明
Next.jsReact 全栈框架,能写页面也能写 API,背靠 Vercel,SEO 极佳
App Router约定大于一切,文件就是路由
layout.tsx布局文件,先渲染布局再渲染页面
page.tsx页面组件,编译成 HTML
route.tsAPI 接口,返回 JSON
'use client'强交互页面声明,先在服务器渲染再去客户端水合
水合(hydration)服务器包好速冻水饺,浏览器下水煮激活交互

Next.js 是给 React 开发者的开箱即用利器:App Router 约定式路由、layout 布局 + metadata SEO、服务端组件默认 SSR、route.ts 直接写 API,一套代码打通全栈。


写在最后(下篇)

今天最大的收获,是亲手搭出了第一个全栈项目。以前觉得"SEO 优化"是运营的事,现在发现前端就能解决——用 Next.js 把 React 组件搬到服务器渲染,爬虫看到的就不再是空 #root,而是完整的内容。很多 AI 产品官网都用 Next.js,不是没有道理的:SEO 好、能全栈、背靠 Vercel 部署方便。老师说后面还有一章 Next.js 的内容,估计是数据获取、动态路由这些更进阶的玩法,学完继续更!

下次面试官问你:"Next.js 的 App Router 是什么?'use client' 是干嘛的?怎么用 Next.js 写 API?"

你可以淡定地说:

"Next.js 的 App Router 约定大于一切:不需要建路由配置文件,文件本身就是路由,page.tsx 就是页面、layout.tsx 是布局、route.ts 是 API 接口,嵌套路由直接建文件夹。默认情况下组件都是服务端组件,在服务器上编译成 HTML,为了 SEO;强交互页面在文件顶部写 'use client' 声明,组件会先在服务器渲染一遍、再到客户端水合(hydration)激活交互——就像包好的速冻水饺,服务器发货,浏览器拿回家下水煮。写 API 也很简单,route.ts 里导出 GET、POST 异步函数,Response.json() 直接返回 JSON,前端页面 fetch 自己的接口就能形成全栈闭环。"

然后看着面试官满意的表情,心里默念:这波,又稳了。


本文所有代码示例均来自课堂学习资料,真实可运行。