WenDoraAi官网项目使用的是 Next.js 最新的 App Router (即 app 目录),它引入了一个核心概念:服务端组件 (Server Components),不是 pages 目录。SSR写法的也会有不一样的区分。再次声明一下,WenDoraAi官网非全栈项目,是前后端分离的纯前端项目哈,这个系列的实战适用于前端去写SEO官网。
一、什么是SSR?
SSR(Server-Side Rendering,服务端渲染)指的是:在服务器上将 React 组件渲染成 HTML,再把这份 HTML 发送给浏览器。这样,用户访问页面时,首屏就能看到完整内容(用户无需等待 JS 加载和执行就能看到内容),有利于 SEO 和首屏加载速度。
以下这些官方文档都包含了 SSR 的原理、用法、最佳实践和代码示例:
-
SSR 基础介绍
https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering -
getServerSideProps 用法(pages 目录)
https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props -
App Router(app 目录)服务端组件数据获取
https://nextjs.org/docs/app/building-your-application/rendering/server-components -
官方数据获取总览
https://nextjs.org/docs/app/building-your-application/data-fetching/overview=
二、SSR 与 CSR 的区别
- SSR:数据和 HTML 都在服务器生成,首屏内容完整,SEO 友好。
- CSR(客户端渲染):数据在浏览器端用 useEffect、fetch、axios 获取,首屏 HTML 只有空壳,SEO 不友好,一般用在**后台管理系统,**不需要搜索引擎收录。
三、Next.js硬性约定
非常重要!!!我在未了解之前被坑了,因为不生效报错,导致重写了一遍。
如 public、app/pages、.next),Next.js 还有一些其他"硬性要求"或重要约定:
-
特殊文件命名:
- app 目录 :
layout.tsx(或.js)、page.tsx(或.js)是必需的。loading.tsx、error.tsx、not-found.tsx、template.tsx也是具有特殊功能的保留文件名。 pages目录 :_app.tsx和_document.tsx是特殊文件,用于自定义应用和 HTML 文档结构。
- app 目录 :
-
环境变量:
- 只有以
NEXT_PUBLIC_为前缀的环境变量才会暴露给浏览器端代码。这是一个安全机制,防止服务端密钥泄露。这里在未了解之前被坑了,用了随意的写法导致请求封装那里不成功
- 只有以
-
组件类型(App Router):
- 默认情况下,app 目录下的所有组件都是服务端组件 (Server Components)。
- 如果组件需要使用
useState、useEffect、事件监听等浏览器端功能,必须在文件顶部声明'use client';,将其标记为客户端组件 (Client Components)。
-
数据获取函数:
- 在
pages目录中,getServerSideProps和getStaticProps是具有特殊功能的导出函数,只能在页面文件(pages/xxx.tsx)中使用。
- 在
-
next.config.js:- 项目根目录下的
next.config.js(或.mjs,.ts)是官方指定的配置文件,用于修改 Next.js 的默认行为。Next.js 的开发服务器出于安全考虑,默认只允许来自localhost的访问,除非修改next.config.js(或.mjs,.ts)或修改 package.json内容:
- 项目根目录下的
javascript
"dev": "next dev --hostname 0.0.0.0"
//0.0.0.0 会让服务监听你所有的网络接口
next.config.js内容:
javascript
const nextConfig = {
experimental: {
allowedDevOrigins: ["10.44.23.161"],
},
};
/* config options here */
export default nextConfig;
四、HTTP请求封装
HTTP请求封装我通过查阅有多种做法,由于此次的WendoraAi官网项目是前后端分离的项目,此次是纯前端项目做法,Next.js 官方文档推荐使用 fetch(因为它是原生 API,支持 SSR),但明确表示可以用任何 HTTP 客户端,包括 axios。
官方文档:https://nextjs.org/docs/app/getting-started/fetching-data
根据之前vue的使用习惯,决定使用axios。和fetch相比,axios功能丰富,社区成熟,也能支持SSR
4.1 创建目录文件
新增
Matlab
app/
api/
home.ts // 首页相关接口
...
utils/
request.ts // axios 封装
.env.development //环境变量文件
.env.production

.env.development 和 .env.production
javascript
NEXT_PUBLIC_API_BASE="https://你自己的网址"
javascript
request.ts:
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
//注意// SSR 场景下不能用 window/localStorage,token,要用参数去传
export function createAxios(token?: string) {
const service = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE,
timeout: 10000,
});
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
console.log('--- 发起请求 ---');
console.log('请求 URL:', config.url);
console.log('请求方法:', config.method);
console.log('请求参数:', config.params);
console.log('-----------------');
config.headers = config.headers || {};
// 国际化
config.headers['Content-Language'] = 'zh-CN';
// token
if (token) config.headers['Authorization'] = `Bearer ${token}`;
// 其他自定义逻辑...
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
console.log('--- 收到响应 ---');
console.log('响应数据:', response.data);
console.log('-----------------');
const code = response.data.code ?? 0;
if (code === 401) {
// 这里可以抛出错误,前端跳转登录
throw new Error('未登录或登录已过期');
}
if (code !== 200) {
// 抛出更详细的错误,方便调试
const errorMessage = response.data.msg || `请求错误,状态码: ${code}`;
throw new Error(errorMessage);
}
return response.data;
},
(error) => {
throw error;
}
);
return service;
}
export default createAxios();
上面的请求拦截器为啥要有打印捏,主要是因为这个请求是在服务器端完成的,而不是在浏览器里发出的。
这是 Next.js 服务端渲染(SSR)的核心工作方式:
-
请求流程:
- 浏览器请求页面 (
http://localhost:3000/)。 - Next.js 服务器接收到请求,开始渲染 page.tsx 这个服务端组件 (Server Component)。
- 在服务器上,
async function Home()被执行,await getHomeText('private')这行代码会在服务器上 向后端接口 (https://你自己的网址/...) 发起 HTTP 请求。 - 服务器拿到接口数据后,将数据和 HTML 渲染成一个完整的页面。
- 最后,服务器把这个已经包含数据的完整 HTML 发送给浏览器。
- 浏览器请求页面 (
-
为什么 Network 面板看不到?
- 因为整个数据请求过程都发生在服务器内部,浏览器对此毫不知情。浏览器只负责接收并显示最终的 HTML 结果。
- 浏览器的 Network 面板只能监控由浏览器本身发起的请求。
所以才有了打印,为了更好的去进行数据排查等
javascript
home.ts:
import request from '../utils/request';
// 获取首页文本信息
export function getHomeText(infoKey: string) {
return request({
url: '/api/info/getText',
method: 'get',
params: { infoKey }
});
}