趁你现在对我们刚刚讨论的有关 React 和 SEO 的问题还保持有较强的印象,我们先来讨论 NextJS 是如何利用多种渲染模式来应用 SEO 挑战的。
如果你已经忘了我们之前讨论的 React 和 SEO 的问题,可以点击这里回顾一下。
NextJS 的渲染模式
NextJS 提供了几种渲染模式,你可以针对不同场景进行选择:
渲染模式 | 说明 | 应用场景举例 | 是否SEO友好 |
---|---|---|---|
静态网站生成 | 将我们的组件和数据(包括 state 、props 等,下同)生成静态的 HTML 文件。 | 可用于静态文件生成,实现诸如 hugo、hexo 等静态网站生成器的功能。 | 是 |
客户端渲染 | 就是传统的 React 的渲染模式。 | 适用于不关心 SEO 的项目,比如后台管理、企业内部系统等。 | 否 |
服务端渲染 | 利用内置的服务器,将我们的组件和数据动态地生成 HTML。 | 类似于使用 AXUM 开发网站。适合需要 SEO,数据实时更新的项目。 | 是 |
静态网站生成 Static Site Generation
NextJS 支持将整个项目生成为一个纯 HTML 的静态网站。我们通过一个小案例来体验一下这个强大的功能。本案例就是显示一个用户列表,为了验证静态网站生成是否真的会将组件和数据都生成静态HTML,我们特意使用了两个组件:用于显示用户列表的 UserList
及其子组件 UserItem
:
// components/UserItem.jsx
export default function UserItem({ user }) {
return (
<li>
ID: {user.id}, 姓名: {user.name}
</li>
);
}
// components/UserList.jsx
import UserItem from './Item';
export default function UserList() {
const users = [
{ id: 1, name: 'axum中文网1' },
{ id: 2, name: 'axum中文网2' },
{ id: 3, name: 'axum中文网3' },
{ id: 4, name: 'axum中文网4' },
{ id: 5, name: 'axum中文网5' },
{ id: 6, name: 'axum中文网6' },
];
return (
<ul>
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
}
// pages/index.jsx
import { UserList } from '../components/User';
export default function Home() {
return (
<div>
<h1>用户列表</h1>
<UserList />
</div>
);
}
生成静态网站
现在可以使用命令 npx next build && npx next export
生成静态网站了,为了方便使用,我们把它作为自定义命令加到 package.json
中:
"scripts": {
//...
"html": "next build && next export"
},
这样,我们就可以简单的使用下面的命令来生成静态网站了:
yarn html
# 如果你用npm
npm run html
生成的静态文件在 out
目录下,打开 out/index.html
:
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/_next/static/css/ef46db3751d8e999.css" as="style"/><link rel="stylesheet" href="/_next/static/css/ef46db3751d8e999.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/_next/static/chunks/webpack-8fa1640cc84ba8fe.js" defer=""></script><script src="/_next/static/chunks/framework-8c5acb0054140387.js" defer=""></script><script src="/_next/static/chunks/main-a15ee65a2f1e1948.js" defer=""></script><script src="/_next/static/chunks/pages/_app-2398585b21d2d8c8.js" defer=""></script><script src="/_next/static/chunks/pages/index-4c6abd58992a79df.js" defer=""></script><script src="/_next/static/uuuuuuuuuuuuuuuuuuuuu/_buildManifest.js" defer=""></script><script src="/_next/static/uuuuuuuuuuuuuuuuuuuuu/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div><h1>用户列表</h1><ul><li>ID: <!-- -->1<!-- -->, 姓名: <!-- -->axum中文网1</li><li>ID: <!-- -->2<!-- -->, 姓名: <!-- -->axum中文网2</li><li>ID: <!-- -->3<!-- -->, 姓名: <!-- -->axum中文网3</li><li>ID: <!-- -->4<!-- -->, 姓名: <!-- -->axum中文网4</li><li>ID: <!-- -->5<!-- -->, 姓名: <!-- -->axum中文网5</li><li>ID: <!-- -->6<!-- -->, 姓名: <!-- -->axum中文网6</li></ul></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"uuuuuuuuuuuuuuuuuuuuu","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
非常非常耐丝,我们的组件和数据全部变成了静态 HTML,再也不用担心 SEO了。
本小节代码:axum-rs-nextjs-static-site-gen-1
客户端渲染 Client-Side Rendering
客户端渲染就是 React 的模式,React 怎么玩,客户端渲染也怎么玩
- 如果要从后端获取数据,将 HTTP 请求放在
useEffect
hook 里 - 构建最终项目时,只需要
build
,不需要像生成静态网站那样,还要export
还是使用那个从 https://httpbin.org/ip
获取客户端ip为例:
你会看到,不就是 React 嘛!此例我们使用 HTML 5 提供的 fetch()
来发起 HTTP 请求。
为了告诉打包工具,我们使用的是浏览器提供
fetch()
,所以这里要写上完整的window.fetch()
本小节代码:axum-rs-nextjs-client-side-rendering
服务端渲染 Server-Side Rendering
在服务端渲染模式下,我们通常需要从后端获取数据,并将数据填充给组件,然后动态地将组件转换成 HTML。
要从后端获取数据,自然要有 HTTP 请求库,注意:
- 在服务端渲染模式下,一定要通过
yarn
来安装 HTTP 请求库 - 哪怕你想用
fetch()
也要额外安装,因为服务端渲染实际会开启一个 nodejs 的 HTTP 服务器,而 nodejs 并没有fetch
- 或者也可以安装服务端和客户端都能用的 axios
另外,NextJS 是通过一个特殊的函数来识别出是服务端渲染的,这个函数就是:getServerSideProps()
,函数名简单明了:获取服务端的Props
getServerSideProps
只能定义在页面组件中,为页面组件服务(pages
目录下的组件),对其它组件(比compontents
目录下的组件)无效getServerSideProps
中,必须将那些从服务端获取的数据包裹在props
对象里再进行返回,而不能直接返回从服务端获取的对象- nextjs 会自动将
getServerSideProps
的返回值传递给页面组件,页面组件只需要通过参数就能接收到——就和在 React 中,组件接收props
一样的写法
下面通过两个案例来演示服务端渲染。
第一个案例是将上面的静态代码生成变成服务端渲染。
第二个案例是将上面的获取IP的客户端渲染变成服务端渲染。
案例1:使用服务端渲染实现用户列表
首先,我们给 pages/index.js
加上 getServerSideProps()
:
import { UserList } from '../components/User';
export default function Home({ users }) {
return (
<div>
<h1>用户列表</h1>
<UserList users={users} />
</div>
);
}
export async function getServerSideProps() { // 第11行
const users = [
{ id: 1, name: 'axum中文网1' },
{ id: 2, name: 'axum中文网2' },
{ id: 3, name: 'axum中文网3' },
{ id: 4, name: 'axum中文网4' },
{ id: 5, name: 'axum中文网5' },
{ id: 6, name: 'axum中文网6' },
];
return { props: { users } }; // 第20行
} // 第21行
-
export default function Home({ users })
:注意此时Home
页面组件,它:-
从外界接收了
props
。这个props
将由 nextjs 自动把getServerSideProps()
的返回值传递过来。 -
将
props
解构获取users
-
如果你对解构还不是很清楚,可以将上面的代码看成:
-
-
<UserList users={users} />
:将这个从props
里接收到的users
原封不动地传递给UserList
组件 -
第11~21行:实现特殊的
getServerSideProps()
-
它用于从服务端获取数据,本案例作为演示,直接返回一个模拟的数据
-
它的返回值会作为参数发送给页面组件,此例中的
Home
组件 -
第20行:必须将数据包裹在
props
对象中进行返回// 错误! return {user}; // 正确:要将数据作为 props 对象的成员进行返回 return { props: {user}};
-
然后对 UserList
进行精简——从父组件接收数据
import UserItem from './Item';
export default function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
}
本小节代码:axum-rs-nextjs-server-side-rendering-1
案例2:使用服务端渲染获取IP
import axios from 'axios';
export default function Home({ ip }) {
return <h1>你好,你的IP是 {ip}</h1>;
}
export async function getServerSideProps() { // 第7行
// 从服务端获取数据
const { data } = await axios.get(`https://httpbin.org/ip`);
const { origin } = data;
// 通过 props 对象,将获取到的数据传递给页面组件
return { props: { ip: origin } }; // 第13行
} // 第14行
export default function Home({ ip })
:从props
解构出ip
- 第7~14行:实现
getServerSideProps
函数,它会从服务端获取数据,并将结果返回。页面组件将能接收到它的返回结果- 第13行:再次提醒,从服务端获得的数据不能直接返回,而是要包裹在
props
对象里
- 第13行:再次提醒,从服务端获得的数据不能直接返回,而是要包裹在
记得安装 axios
yarn add axios
本小节代码:axum-rs-nextjs-server-side-rendering-2
特殊的函数
- 【服务器渲染】
getServerSideProps()
:用于填充服务器渲染时所需要的动态props
,通常应该在这里发起 HTTP 请求,从远程服务端获取数据。NextJS 能保证,某用户在访问带有getServerSideProps()
的页面时,总是会从远程服务端获取最新数据。 - 【静态网站生成】
getStaticProps()
:用于填充静态网站生成时所需要的props
,作用和getServerSideProps()
相似。注意,由于是静态网站生成,NextJS 只会将那些在构建项目时,从getStaticProps()
获取的数据转换成 HTML,如果远程服务器数据发生变化,NextJS 不会自动更新 HTML。 - 【静态网站生成】
getStaticPaths()
:如果在生成静态网站时,有特殊要处理的路径,可以在这里指定。 - 【客户端渲染】典型的 React,不需要特殊的函数,使用 React 提供的
useEffect
hook 即可获取远程服务端的数据。
渲染模式的选择
问题来了,该如何选择 NextJS 的渲染模式呢?从2个维度供你参考:
是否要求数据实时更新 | 是否要求SEO友好 | 渲染模式 |
---|---|---|
✅ | ❌ | 客户端渲染 |
❌ | ✅ | 静态网站生成 |
✅ | ✅ | 服务端渲染 |