服务端渲染 (SSR)

311 阅读4分钟

1. 服务端渲染 (SSR)

  • SSR 是 Server-Side Rendering,即服务端渲染的英文缩写
  • 好处:更快的首屏加载、更好的 SEO
  • vs 静态站点生成 (Static-Site Generation,缩写为 SSG):也被称为预渲染,页面内容对每个用户相同,则只构建一次。预渲染的页面生成后作为静态 HTML 文件被服务器托管
  • 常用实现框架:vue/server-renderernuxtnext

2. 三种实现 基本使用

  • vue/server-renderer

import express from 'express'
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'

const server = express()

server.get('/', (req, res) => {
  const app = createSSRApp({
    data: () => ({ count: 1 }),
    template: `<button @click="count++">{{ count }}</button>`
  })

  renderToString(app).then((html) => {
    res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>Vue SSR Example</title>
      </head>
      <body>
        <div id="app">${html}</div>
      </body>
    </html>
    `)
  })
})

server.listen(3000, () => {
  console.log('ready')
})
  • nuxt

vue架构,asyncData 返回的对象可以像data一样使用

<script>
import axios from 'axios'
export default {
  data(){
     return {
         name:'hello World',
     }
  },
  async asyncData(){
      let {data}=await axios.get('https://api.myjson.com/bins/8gdmr')
      return {info: data}
  }
}
</script>
async asyncData(context) {
  let [newDetailRes, hotInformationRes, correlationRes] = await Promise.all([
    axios.post('http://www.huanjingwuyou.com/eia/news/detail', {
      newsCode: newsCode
    }),
    axios.post('http://www.huanjingwuyou.com/eia/news/select', {
      newsType: newsType, // 资讯类型: 3环评资讯 4环评知识
      start: 0, // 从第0条开始
      pageSize: 10,
      newsRecommend: true
    }),
    axios.post('http://www.huanjingwuyou.com/eia/news/select', {
      newsType: newsType, // 资讯类型: 3环评资讯 4环评知识
      start: 0, // 从第0条开始
      pageSize: 3,
      newsRecommend: false
    })
  ])
  return {
    newDetailList: newDetailRes.data.result,
    hotNewList: hotInformationRes.data.result.data,
    newsList: correlationRes.data.result.data,
  }
},
  • next

react 架构

  • getStaticPaths 决定哪些路径提前渲染
  • getInitialProps
function Page({ stars }) {
  return <div>Next stars: {stars}</div>
}

Page.getInitialProps = async (ctx) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
}

export default Page
  • getServerSideProps
    打包的时候,请求接口数据生成页面

  • getStaticProps
    每次客户端请求的时候,请求接口更新页面

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
  }
}

export default Blog
  • useSWR
import useSWR from 'swr'

const fetcher = (...args) => fetch(...args).then((res) => res.json())

function Profile() {
  const { data, error } = useSWR('/api/profile-data', fetcher)

  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  )
}

3. 原理

image.png

3.1 流程

1、用户打开浏览器,输入网址请求到Node.js

2、部署在Node.js的应用Nuxt.js接收浏览器请求,并请求服务端获取数据

3、Nuxt.js获取到数据后进行服务端渲染

4、Nuxt.js将html网页响应给浏览器

3.2 nuxt生命周期

  • 在服务端生成初始静态html
  • 客户端 mounted阶段挂载dom
  • mvvm监听数据变化,更新视图

服务端

对于 SSR,将为您的应用程序的每个初始请求执行这些步骤

  • 服务器启动 ( nuxt start)

使用静态站点生成时,服务器步骤仅在构建时执行,但对于将生成的每个页面执行一次

  • 生成过程开始 ( nuxt generate)

  • Nuxt 挂钩

  • 服务器中间件

  • 服务器端 Nuxt 插件

    • 按照 nuxt.config.js 中定义的顺序
  • nuxtServerInit

    • 仅在服务器端调用以预填充存储的 Vuex 操作

    • 第一个参数是Vuex 上下文,第二个参数是Nuxt 上下文

      • 从此处调度其他操作 → 仅为服务器端后续存储操作的“入口点”
    • 只能定义在store/index.js

  • 中间件

    • 全局中间件
    • 布局中间件
    • 路由中间件
  • asyncData (blocking)

  • beforeCreate (Vue lifecycle method)

  • created (Vue lifecycle method)

  • 新的 fetch(从上到下,兄弟 = 并行)

  • 状态序列化(render:routeContextNuxt 挂钩)

  • HTML 渲染发生(render:routeNuxt 钩子)

  • render:routeDone当 HTML 被发送到浏览器时钩子

  • generate:beforeNuxt 钩子

  • 生成 HTML 文件

    • 全静态生成
    • generate:page( 编辑HTML)
    • generate:routeCreated(路由生成)
  • generate:done当所有 HTML 文件都生成时

客户端

这部分生命周期完全在浏览器中执行,无论您选择了哪种 Nuxt 模式。

  • 接收 HTML

  • 加载资源(例如 JavaScript)

  • 客户端 Nuxt 插件

    • 按照 nuxt.config.js 中定义的顺序
  • Vue Hydration

将一些事件绑定Vue状态注入到输出的静态的页面中, 同步下发给浏览器的的Vue bundle接管状态, 继续处理接下来的交互逻辑

  • 中间件

    • 全局中间件
    • 布局中间件
    • 路由中间件
  • asyncData (blocking)

  • beforeCreate(Vue 生命周期方法)

  • created(Vue生命周期方法)

  • 新的 fetch(从上到下,兄弟 = 并行)(非阻塞)

  • beforeMount(Vue 生命周期方法)

  • mounted(Vue生命周期方法)

4.next部署

  • node环境
  1. Dockerfile
# 1.需要装依赖
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
else echo "Lockfile not found." && exit 1; \
fi


# 2.不需要重装依赖
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .


RUN yarn build


# 3.使用生产镜像,已有依赖
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]
  1. Build your container: docker build -t nextjs-docker .

  2. Run your container: docker run -p 3000:3000 nextjs-docker

欢迎关注我的前端自检清单,我和你一起成长