Next.js静态页面渲染技术(静态生成和服务端渲染):BSR/SSR/SSG
Author:zhoulujun Date:
next是一款用JS开发的全栈框架,它是基于express框架基础上开发而成,可以用react写客户端,node.js写服务端。一份代码可在前后端同时运行,这在next中称之为同构!
next.js框架基础介绍
创建项目:npm init next-app 项目名,项目创建好后next会帮你搭好基础通用的模板,大多常用的api以及写法都能在模板中找到。
自定义head:使用<Head>组件可自定义<title><meta>标签和内容组件导入。
// 文件路径 page/_app.js import Head from "next/head"; import '../styles/globals.scss' export default function App({ Component, pageProps }) { return <> <Head> <title>我的博客 A-Tione</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"/> </Head> <Component {...pageProps} /> </> }
快速导航:标准写法<Link href=xxx><a>xxx</a></Link>,在next中通过<Link>标签的href链接定位路由可实现预加载路由页面,可使点击跳转无需发送请求。
同构代码:一份代码两端运行,在一处使用console.log调试,可在服务端和客户端页面都显示出log,这样更方便调试。但请注意同构代码时需要使用前后端都存在的对象,比如window、document等客户端才有的对象就无法在服务端log出来。
局部css:因为next是与react配套使用的,我们可用这样写css:<style jsx> 或者 xxx.module.css文件。
全局组件:若需要多处地方使用到同一组件,可使用全局组件,全局组件在路径page/_app.js中声明。
Next.js 三种渲染
BSR(客户端渲染):只在浏览器上执行的渲染
Broswer Side Rende,用JS、Vue、React创建HTML)
SSG(静态页面生成)成是一种在build阶段生成html的预渲染方式。预渲染的HTML的代码会被每个request复用。
Static Stie Generation,解决白屏问题、SEO问题。但无法生成用户相关内容(所以用户请求的结果都相同)。
适合前后端交互不多的页面,如CMS(文章管理系统)生成静态页面、
SSR(服务端渲染)是每次请求都生产新HTML(传统BS框架PHP、Python、Ruby、Java后台的基本功能)
Sever Site Render,解决白屏问题、SEO问题。并且可以生成与用户相关的请求内容(不同用户结果不同)。但是服务端请求压力大
SSR与SSG都属于预渲染Pre-rendering,Next.js的预渲染可以与前端React无缝对接。
Next.js有两种预渲染形式:静态生成(Static Generation)和服务端渲染(Server-side Rendering)。 不同之处在于他们为页面生成HTML代码的时间
客户端渲染(BSR)
客户端渲染,顾名思义就是只在浏览器上执行的渲染,指用浏览器JS创建的HTML代码。
通过Vue 、 React、angularJS 构建的单页面应用SPA 都是采用这种方式渲染
前端:
import {NextPage} from 'next'; import {usePosts} from '../../hooks/usePosts'; const PostsIndex: NextPage = () => { const {posts, isLoading, isEmpty} = usePosts(); return ( <div> <h1>文章列表</h1> {isLoading ? <div>加载中</div> : isEmpty ? <div>没有文章</div> : posts.map(p => <div key={p.id}> {p.id} </div>) } </div> ); }; export default PostsIndex;
后端:
import {useEffect, useState} from 'react'; import axios from 'axios'; export const usePosts = () => { const [posts, setPosts] = useState<Post[]>([]); const [isLoading, setIsLoading] = useState(false); const [isEmpty, setIsEmpty] = useState(false); useEffect(() => { setIsLoading(true); axios.get('api/v1/posts').then(response => { setPosts(response.data); setIsLoading(false); if (response.data.length === 0) { setIsEmpty(true); } }, () => { setIsLoading(false); }); }, []); return {posts, setPosts, isLoading, setIsLoading, isEmpty, setIsEmpty}; };
文章列表完全前端渲染的,可称之为客户端渲染。
一般来说,静态内容在代码里写死的,动态内容是来自数据库的。
在next中,图上的静态内容会在服务器渲染一次,客户端再渲染一次,为什么?
在React SSR官方文档中提到:推荐后端使用renderToString(),在前端hydrate()。前端hydrate()混合指的是会保留HTML并附上事件监听,也就是说后端渲染HTML,前端添加监听,前端也会渲染一次来保证前后端渲染结果一致。next框架已经帮我们做好了这一步。
客户端渲染的缺点:
白屏:在ajax得到响应之前,页面中之后Loading。
SEO不友好:因为搜索引擎访问页面, 默认不会执行 JS,只能看到 HTML,而不会等待 AJAX 异步请求数据,所以搜索不到页面内容
在文章列表页面里,其实每个用户查到的内容都是一样的
那为什么还需要在每个人的浏览器上渲染一遍呢?
为什么不在后端渲染好,然后发给每个人
这样就可以
N 次渲染变成了 1 次渲染
N 次客户端渲染变成了 1 次静态页面生成
这个过程成为 动态内容静态化
静态页面生成(SSG)
前提:
如果每个人都请求一个相同的资源,比如都请求相同的文章列表,那还需要在每个人的浏览器上渲染一次吗?直觉告诉我们是不是大可不必,可以直接在后端渲染好,然后每个人直接读取后端传来的内容。
n次渲染变成了一次渲染,n次客户端渲染变成了1次静态页面生成。这个过程叫做动态内容静态化。
如何做SSG:
那么后端渲染还需要通过ajax来获取渲染内容么?也可以,axios支持服务端使用,但是这样有点傻,资源就在服务端为什么还需要绕远路请求ajax来获取一次资源呢?
我们可以在服务端这样写:
通过getStaticProps获取内容。声明位置:每个page不是默认导出一个函数么,把getStaticProps声明在这个函数旁边即可,默认export导出。
import {NextPage} from ' next'; import Link from ' next/link'; import {getPosts} from '../.. /lib/posts'; type Post = { id: string, title: string} type Posts = { posts: Post[]; } const Postslndex: NextPage<Posts> = (props => { const {posts} = props; return ( <div> <h1>文章列表</h1> {posts.map(p=>{( <div key={p.id}> <Link href={ `/post/${p.id}`}>><a>{p.id}</a></Link> </div> )})} </div> ) }) export default Postslndex; export const getStaticProps = async () => { const posts = await getPosts(); return { props: { posts: '内容XXX' } } }
该如何获取 posts 呢?
因为加载数据的操作在后端,想通过 AJAX 获取 posts 显然不合适
答案是: 通过 getStaticProps 获取 posts
getStaticProps 是 Next.js 提供的一个方法,会在后端执行,返回一个 props,NextPage 在渲染的时候可以使用这个 props
getStaticProps:
export default function PostsIndex = (props)=> { ... }
我们可以看到玄机就藏在 id 为 _NEXT_DATA__ 的 script 标签中,里面储存了传给前端的 props 数据
通过同构,前端也可以不用ajax就能拿到数据了,这就是同构的好处:后端数据可以直接传给前端,然后前端JSON.parse一下就能得到了posts(next框架已经帮我们做了parse)。
这就是同构 SSR 的好处,后端可以将数据直接传给前端,而不需要 AJAX 异步获取
为什么不直接把数据放入 posts.js 呢?
显然是为了 posts.js 接受不同的数据,当我们展示每篇博客的时候,他们的样式相同,内容不同,就会用到这个功能
动态内容静态化
如果动态内容与用户无关,那么可以提前静态化
通过 getStaticProps 可以获取数据
静态内容+数据(本地获取) 就得到了完整的页面
代替了之前的 静态内容+动态内容(AJAX 获取)
三种文件类型
build 完成后,我们查看.next 文件里面,发现 posts.html、posts.js、posts.json
posts.html 含有静态内容,用于用户直接访问
post.js 也含有静态内容,用于快速导航(与 HTML 对应)
posts.json 含有数据,跟 posts.js 结合得到页面
PHP/java/Pyton 能不能做?
思路是一样的,他们也能做,但是他们不支持jsx,不好与React无缝对接,而且这些语言的对象不能直接提供给JS用,需要类型转换。
SSG静态化的时机:
开发环境:在开发环境每次请求都会运行一次getStaticProps,这是为了方便修改代码时重新运行。
生产环境:getStaticProps只在build时运行一次,这样可以提供一份html给所有用户下载。
SSG静态化的优点:
生产环境中直接给出完整页面
首屏不会白屏
搜索引擎能看到页面内容,方便SEO
SSG静态化的缺点:
所有用户看到的都是同一个页面,无法生成用户相关内容
如果页面和用户相关呢?
这种情况较难提前静态化,需要在 用户请求时,获取用户信息,然后 通过用户信息去数据库拿数据,如果非要做,就要给每个用户创建一个页面,有时候这些数据更新极快,无法提前静态化, 比如微博首页的信息流
那怎么办?
要么客户端渲染, 会出现白屏
要么服务端渲染 SSR,没有白屏
服务端渲染(SSR)
前提:
如果是与用户相关的动态内容,较难提前静态化,需要在用户请求时,获取用户信息,然后通过用户信息去数据库拿数据。有时候这些数据更新极快,无法提前静态化,比如微博首页。
使用SSR:
这些更新极快的内容我们可以
客户端渲染,下拉更新
服务端渲染,下拉更新
但这次的服务端渲染不能用getStaticProps,因为getStaticProps是在build时执行的,可用getServerSideProps(context: NextPageContent)。
getServerSideProps:
运行时机:无论是开发环境还是生产环境都是在请求到来之后运行getServerSideProps。回顾getStaticProps,它只在生产环境build时运行一次。
参数:context,类型为NextPageContent。content.req / context.res 可以获取请求和响应,一般只需要用到context.req。
import {GetStaticProps, NextPage} from 'next'; import {getPosts} from '../../lib/posts'; import Link from 'next/link'; import * as React from 'react'; type Post = { id: string, title: string } type Props = { posts: Post[]; } // props 中有下面导出的数据 posts const PostsIndex: NextPage<Props> = (props) => { const {posts} = props; // 前后端控制台都能打印 -> 同构 console.log(posts); return ( <div> <h1>文章列表</h1> {posts.map(p => <div key={p.id}> <Link href={`/posts/${p.id}`}> <a> {p.id} </a> </Link> </div>)} </div> ); }; export default PostsIndex; // 实现SSG export const getStaticProps: GetStaticProps = async () => { const posts = await getPosts(); return { props: { posts: JSON.parse(JSON.stringify(posts)) } }; };
SSR 原理
推荐 在后端 renderToString() 在前端 hydrate()
SSR的缺点:
SSR无法获取客户端信息,比如浏览器大小。必须要用户通过客户端实际登录发送具体请求后才能知道客户端的信息,仅通过用户信息是无法得知具体的客户端信息。
参考文章:
node.js Next框架的三种渲染方式:客户端渲染、SSG、SSR https://blog.csdn.net/weixin_41819731/article/details/109899642
Next.js 的三种渲染方式(BSR、SSG、SSR) https://zhuanlan.zhihu.com/p/341229054
转载本站文章《Next.js静态页面渲染技术(静态生成和服务端渲染):BSR/SSR/SSG》,
请注明出处:https://www.zhoulujun.cn/html/webfront/server/nextjs/8793.html