最近在探索学习前端工程化相关内容,在如今前后端分离的架构下,为了提升首屏渲染速度和 SEO 效果,兜兜转转,又回到了服务端渲染。
本文主要是讲讲如何使用 Next.js 框架实现服务端渲染,重构或优化现有前端应用的 SEO 和首屏渲染速度。
一、服务端渲染(SSR)
服务端渲染(SSR,Server Side Render
)与客户端渲染(CSR,Client Side Render
)的核心区分点简单来说就是完整的 HTML 文档在服务端还是浏览器里组装完成。
SSR 的另一概念是同构渲染,可以看看知乎中的讨论:什么是前端的同构渲染?
同构渲染简单来说就是一份代码,服务端先通过服务端渲染(
SSR
),生成 HTML 以及初始化数据,客户端拿到代码和初始化数据后,通过对 HTML 的 DOM 进行 patch 和事件绑定对 DOM 进行客户端激活(client-side hydration
),该整体过程叫同构渲染。
SSR 的原理,本文就不再赘述了,感兴趣的朋友推荐阅读这篇文章:《彻底理解服务端渲染 - SSR原理》
二、Next.js
Next.js 是一款用于生产环境的 React 框架,无需配置,默认提供了生产环境所需所有功能的最佳开发实践:支持静态渲染和服务端渲染、支持 TypeScript、智能打包、路由预加载等功能。
与此同时,Next.js 还提供了如下开箱即用的 SDK 辅助开发 Web 应用:
阅读过 SSR 原理一文可看到配置支持服务端渲染还是挺麻烦的,但借助 Next.js,可以很轻松的上手改造支持现有 Web 应用服务端渲染。
是否采用服务端渲染还得综合考虑收益,服务端渲染毕竟会增加服务器的计算开销,稳定性相较于 CSR 差一些。
三、创建 Next.js 应用
初始化一个 Next.js 应用可以直接通过脚手架快速完成:
npx create-next-app@latest --ts
# or
yarn create next-app --typescript
中途会要求输入项目名,并自动安装所需的模块
执行 yarn dev
后需要手动再浏览器打开网址:http://localhost:3000 ,即可看到如下页面:
首页的内容对应 ./pages/index.tsx
文件
初始的目录结构如下:
.
├── pages // 采用约定式路由(文件系统路由)
│ ├── _app.tsx
│ ├── api // API 目录
| ├── hello.ts
│ └── index.tsx // 首页
├── public // 公共资源
│ ├── favicon.ico
│ └── vercel.svg
├── styles // 样式
│ ├── Home.module.css
│ └── globals.css
├── next-env.d.ts // Next 相关的 TS 定义
├── next.config.js // Next.js 自定义配置
├── node_modules
├── package.json
├── tsconfig.json
├── README.md
└── yarn.lock
四、页面路由
通常我们的 Web 应用是多页面、多路由的,因此会涉及到在各个页面之间跳转,因此有必要熟悉 Next.js 的路由使用方式。
上述讲到了 Next.js 是约定式路由,基于文件系统,对应到 ./pages
目录下,当添加页面文件到 ./pages
目录,Next.js 会自动识别并将对应文件注册的路由上
4.1 索引路由
Next.js 会自动将文件夹内的 “index” 文件注册为文件夹的主页
文件路径 | 对应路由 |
---|---|
pages/index.tsx |
/ |
pages/blog/index.tsx |
/blog |
4.2 嵌套路由
Next.js 支持嵌套文件的路由,如果您创建嵌套文件夹结构,文件仍将自动以相同方式路由解析。
文件路径 | 对应路由 |
---|---|
pages/blog/first-post.tsx |
/blog/first-post |
pages/dashboard/settings/username.tsx |
/dashboard/settings/username |
4.3 动态参数路由
常见于比如博客的文章详情页面,文章的 id 是动态变化的,Next.js 中可以使用中括号解析到对应的命名参数
文件路径 | 对应路由 | |
---|---|---|
pages/blog/[slug].js |
/blog/:slug |
/blog/hello-world |
pages/[username]/settings.js |
/:username/settings |
/foo/settings |
pages/post/[...all].js |
/post/* |
/post/2021/id/title |
更多关于动态路由的解析可参阅:https://nextjs.org/docs/routing/dynamic-routes
4.4 路由跳转
之前有提到 Next.js 中的路由预加载功能,需借助 Next.js 提供的 next/link
,写法如下:
第一篇文章
应用页面之间的跳转,可以用 标签包裹。
属性 href
的值是跳转页面的路径字符串或 URL 对象:
import Link from 'next/link'
function Articles({ articles }) {
return (
{articles.map((article) => (
-
{article.title}
))}
)
}
export default Articles
如有需要对路由通过 js 跳转,则可以通过 Next.js 提供的 next/router 中的 useRouter Hook。
4.5 代码拆分和预加载
通过 Next.js 的路由功能,可以自动完成页面按需加载当前页面所需的代码,同时会自动预加载页面中属于自身应用的链接。
这意味着在呈现主页时,最初不会提供其他页面的代码,同时可确保即使您有数百个页面,主页也能按需快速加载。
仅加载您请求的页面的代码也意味着页面变得独立,如果某个页面抛出错误,应用程序的其余部分仍然可以工作。
在 Next.js 的生产版本中,每当 Link
组件出现在浏览器的视口中时,Next.js 都会在后台自动预取链接页面的代码。当您单击链接时,目标页面的代码已在后台加载,页面转换将近乎即时。
五、静态资源
所有静态资源都可以放到 ./public
目录下,Next.js 会自动为其中的文件注册路由,按照文件系统的方式,与 Page 的路由类似。
5.1 图片元素
一般网页中的图片写法如下:
但这种写法会需要开发者手动去优化,比如按需加载、错误处理等。
Next.js 考虑到这点,为了减轻开发者负担,于是提供了 next/image,开箱即用。
这里其实可以借鉴一下,别的项目中为了业务统一处理图片,可以封装一个 Image 组件,提升研发效率。
import Image from 'next/image'
const YourComponent = () => (
)
export default YourComponent;
5.2 Meta 数据
网页的 Meta 数据,也就是在 html
->head
标签中的内容
Next.js 提供了 next/head 用于声明式编写网页的 head 内容。
import Link from 'next/link'
import Head from 'next/head'
export default function FirstPost() {
return (
<>
First Post
First Post
Back to home
>
)
}
此外,若我们有需要修改 的诉求时,可创建
pages/_document.js
文件,并通过“自定义文档”的方式继承并统一改造所有网页输出的公共内容。
5.3 JS 脚本文件
例如我们使用了三方库 Jquery,虽然可以直接在 组件中直接写:
但是,这种方式包含脚本并不能明确说明何时加载同一页面上获取的其他 JavaScript 代码。如果某个特定脚本会阻塞渲染并且会延迟页面内容的加载,则会显着影响性能。
因此,可以通过 next/script 来优化
import Link from 'next/link'
import Head from 'next/head'
import Script from 'next/script'
export default function FirstPost() {
return (
<>
First Post