作为一个成熟的前端开发工程师,服务端渲染是必须要具备技能,服务端渲染在开发 ToC 应用时非常有优势,对比客户端渲染有更好的性能(首屏渲染更快)、搜索引擎优化(SEO)更友好。
一、介绍一下服务端渲染(SSR)及静态生成(SSG)
我们一般用 Vue 或 React 开发的应用可以称为客户端渲染(CSR),也就是服务端返回一个基础的 HTML,然后通过 JS 来动态获取数据最终渲染出完整的页面。这种方式问题是首屏渲染可能比较慢,因为需要服务端先返回基础的 HTML 及 JS 等各种资源,客户端拿到后在执行时再根据 JS 中请求获取数据,最终拿到数据后再渲染页面。另外对搜索引擎优化也不友好,因为搜索引擎爬虫在爬取我们页面时只能获取到一个基础的 HTMl 结构,我们的很多通过 JS 动态生成的内容,搜索引擎很难爬取。
在服务端渲染(SSR)中,服务端会先去其他服务或数据库获取数据来渲染出完整的 HTML 后,把这个完整的 HTML 返回给客户端。这样客户端就能直接根据这个 HTML 渲染页面了,搜索引擎的爬虫也更容易知道我们的页面内容和结构,更有利于搜索引擎的优化。
静态生成(SSG)是指在前端构建的时候,就根据从服务端获取的数据生成完整的 HTML,这种方式比较适合于内容不需要个性化展示的页面,比如网站的帮助页、介绍页等,如果里面的内容有变化,需要我们重新构建部署。
Next.js 目前也是有两种路由方式:App 路由和 Pages 路由。App 路由是 Next.js 13 之后支持的新路由方式,也是目前官方最推荐的路由方式。但 Pages 路由还是有很多项目在用,所以这里会分别介绍一下两种路由实现服务端渲染(SSR)和静态生成(SSG)的方式。
二、App 路由实现服务端渲染(SSR)和静态生成(SSG)
1. 实现服务端渲染
- 创建 App 路由项目
执行npx create-next-app@latest
创建Next.js
项目。
执行完上面的操作时记得选择 App 路由方式,完成后进入项目目录,执行npm run dev
启动项目。
- 创建一个 about 页面
在 app 目录下创建一个 about 文件夹,然后在 about 文件夹下创建一个 page.tsx 文件。
写入以下内容:
tsx
export default function About() {
return <div>About</div>;
}
- 分别实现 Server Component 和 Client Component
在 App 路由中,把组件分为 Server Component 和 Client Component。Server Component 是在服务端渲染的组件,一般我们会把数据获取放在服务端组件中,App 路由中默认组件就是服务端组件,Client Component 是在客户端渲染的组件,一般我们会把客户端组件嵌套在服务端组件中,并在组件顶部指定use client
,把需要使用 hook,或者用户操作的操作放在客户端组件中。
服务端组件 app/about/page.tsx:
tsx
import AboutClient from "./client";
export interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
export default async function About() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data: IItem[] = await response.json();
return (
<ul>
{data?.map((item) => (
<li key={item.id}>
<AboutClient item={item} />
</li>
))}
</ul>
);
}
在服务端组件中实现数据获取,并把获取到的数据传递到客户端组件中,客户端组件再去处理相应的删除逻辑等。
客户端组件 app/about/cliont.tsx:
tsx
"use client";
import { useState } from "react";
import { IItem } from "./page";
export default function AboutClient({ item }: { item: IItem }) {
const [isDelete, setIsDelete] = useState(false);
return (
<div style={{ background: "#333", color: "#fff", marginBottom: "10px" }}>
<h1 style={{ textDecoration: isDelete ? "line-through" : "none" }}>
{item.title}
</h1>
{!isDelete && <button onClick={() => setIsDelete(true)}>删除</button>}
</div>
);
}
客户端组件需要在顶部指定use client
,这样 Next.js 就会识别为客户端组件,客户端组件中就可以使用客户端的 hook,比如 useState 等,同时在这里我们模拟处理一下删除逻辑。
如此访问http://localhost:3000/about
就实现了 App 路由的服务端渲染。App 路由的服务端渲染主要就在于客户端组件和服务端组件的拆分和组合。
2. 实现静态生成
app/posts/[id]/page.tsx
tsx
export interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
export async function generateStaticParams() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts: IItem[] = await response.json();
return posts?.map((post) => ({
id: post.id.toString(),
}));
}
export default async function Page({ params }: { params: { id: string } }) {
const { id } = await params;
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
const post: IItem = await response.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
三、Pages 路由实现服务端渲染(SSR)和静态生成(SSG)
1. 实现服务端渲染
- 创建 Pages 路由项目
执行npx create-next-app@latest
创建Pages 路由 Next.js
项目。
执行上面操作时选择不使用 App 路由
即为Pages 路由
。
- 创建 about 页面实现服务端渲染
在 pages 目录下创建 about.tsx 文件。并写入一下内容:
pages/about.tsx
tsx
import { useState } from "react";
export interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
const About = ({ data }: { data: IItem[] }) => {
const [isDelete, setIsDelete] = useState(false);
return (
<ul>
{data?.map((item) => (
<li
key={item.id}
style={{ background: "#333", color: "#fff", marginBottom: "10px" }}
>
<h1 style={{ textDecoration: isDelete ? "line-through" : "none" }}>
{item.title}
</h1>
{!isDelete && <button onClick={() => setIsDelete(true)}>删除</button>}
</li>
))}
</ul>
);
};
export async function getServerSideProps() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data: IItem[] = await response.json();
return {
props: {
data,
},
};
}
export default About;
pages 路由是通过getServerSideProps
来在服务端获取数据并返回一个 props 的属性,然后在组件中获取到这个数据并进行渲染。并且可以这种方式不用拆分组件可以直接使用客户端的 hook 等。
2. 实现静态生成
在 pages 目录下创建 posts/[id].tsx 文件。并写入以下内容:
pages/posts/[id].tsx:
tsx
export interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
const PostPage = ({ post }: { post: IItem }) => {
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
};
export async function getStaticPaths() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts: IItem[] = await response.json();
const paths = posts.map((post) => ({ params: { id: post.id.toString() } }));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }: { params: { id: number } }) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`
);
const post: IItem = await response.json();
return {
props: {
post,
},
};
}
export default PostPage;
在静态生成时需要定义getStaticPaths
和getStaticProps
,getStaticPaths
是用于生成静态的路径,在这个我们获取博客列表,并返回 params 对象,中间带有博客 id,然后在getStaticProps
中根据这个博客 id 获取详情数据并返回给组件,然后组件中拿到博客详情再进行渲染。
最后执行npm run build
就会生成.next
目录,我们部署后或直接npm run start
就可以直接看到这个静态页面了。
如此就实现了两种路由的服务端渲染和静态生成。其实还是非常简单的,希望大家给我点赞和收藏,有问题的话也可以私信我沟通哦!谢谢大家!