Let my path choose you
HOMEPAGE
TYPESCRIPT
CSR
步骤1: 浏览器请求 HTML
↓
步骤2: 服务器返回空壳 HTML
↓
步骤3: 浏览器加载 Vue.js
↓
步骤4: 浏览器执行 JS,请求数据
↓
步骤5: 浏览器渲染页面内容
<!-- CSR模式下的初始HTML -->
<!DOCTYPE html>
<html>
<body>
<div id="app">
<!-- 这里初始是空的 -->
</div>
<script src="vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return { products: [] };
},
mounted() {
// 在客户端请求数据
fetch("/api/products")
.then((res) => res.json())
.then((data) => (this.products = data));
},
}).mount("#app");
</script>
</body>
</html>
SSR
步骤1: 浏览器请求 HTML
↓
步骤2: 服务器执行 Vue 代码
↓
步骤3: 服务器获取数据
↓
步骤4: 服务器生成完整 HTML
↓
步骤5: 浏览器接收并显示完整内容
↓
步骤6: 加载 JS 后激活交互功能
// SSR服务器端代码
import { createSSRApp } from "vue";
import { renderToString } from "vue/server-renderer";
async function render(url) {
const app = createSSRApp({
data() {
return {
products: [],
};
},
});
// 在服务器端获取数据
const products = await fetch("http://api/products").then((res) => res.json());
app.data().products = products;
// 生成HTML字符串
const html = await renderToString(app);
// 返回完整的HTML
return `
<!DOCTYPE html>
<html>
<body>
<div id="app">${html}</div>
<script>
// 注入初始状态
window.__INITIAL_STATE__ = ${JSON.stringify(products)}
</script>
<script src="vue.js"></script>
<script src="app.js"></script>
</body>
</html>
`;
}
all in all
简而言之,做一个常规的企业官网,复杂度相对而言是比较低的,难点在于视觉、动画,并且截至 2022 年 9 月 8 日浏览器的SEO(Search Engine Optimization)
对SPA(Single Page Application)应用
-CSR(Client Side Render 客户端渲染)
还不友好,为了SEO
需要上Multi Page Application
-SSR(Server Side Render)
,,是否需要使用SSG(Static Site Generation)
不适合动态获取数据的场景Nuxt.js
Next.js
这样的服务端渲染框架要考虑实际情况,切勿舍近求远。
区别的可视化展示
CSR 初始 HTML
<!-- 浏览器收到的初始HTML -->
<div id="app">
<!-- 空的 -->
</div>
SSR 初始 HTML
<!-- 浏览器收到的初始HTML -->
<div id="app">
<div class="product-list">
<div class="product">
<h2>商品1</h2>
<p>价格: ¥99</p>
</div>
<div class="product">
<h2>商品2</h2>
<p>价格: ¥199</p>
</div>
</div>
</div>
SSR 的主要优势
- 更快的首屏加载
- 用户立即看到完整内容
- 不需要等待 JS 执行完毕
- 更好的 SEO
- 搜索引擎可以直接爬取完整内容
- 对内容网站特别重要
- 更好的用户体验
- 无需等待空白页面
- 特别适合网速较慢的场景
SSR 的主要劣势
- 服务器压力 SSR 需要服务器执行 Vue 代码和数据获取,需要合理控制服务器负载
- 开发复杂度
Question
即使我用 Nuxt,我的数据也是请求的单独的后端服务,就是在 nuxt 中也只是调用数据接口,这样仍然算是服务端渲染吗
以下内容来自 claude-3.5-sonnet
这仍然是服务端渲染!让我解释原因:
1. Nuxt 的渲染流程
浏览器请求页面
↓
Nuxt服务器接收请求
↓
执行页面组件的asyncData/fetch
↓
调用后端API获取数据
↓
Nuxt服务器用数据渲染完整HTML
↓
返回完整HTML给浏览器
↓
浏览器展示内容并激活(hydration)
2. 代码示例对比
传统客户端渲染:
<template>
<div class="products">
<!-- 初始加载时这里是空的 -->
<div v-for="product in products" :key="product.id">
{{ product.name }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
products: [],
};
},
mounted() {
// 在浏览器端请求数据
fetch("http://api.example.com/products")
.then((res) => res.json())
.then((data) => (this.products = data));
},
};
</script>
Nuxt 服务端渲染:
<template>
<div class="products">
<!-- 服务器端已经填充好数据 -->
<div v-for="product in products" :key="product.id">
{{ product.name }}
</div>
</div>
</template>
<script>
export default {
async asyncData({ $axios }) {
// 在服务器端执行!
const products = await $axios.$get("http://api.example.com/products");
return { products };
},
};
</script>
3. 关键区别
浏览器收到的 HTML 内容:
客户端渲染:
<div class="products">
<!-- 空的 -->
</div>
Nuxt SSR:
<div class="products">
<div>iPhone 13</div>
<div>MacBook Pro</div>
<div>iPad Air</div>
<!-- 数据已经填充 -->
</div>
4. 为什么这仍然是 SSR?
- 数据获取时机
// Nuxt在服务器端完成这些步骤:
async function serverRender() {
// 1. 执行asyncData/fetch
const data = await fetchData();
// 2. 使用数据渲染HTML
const html = await renderComponent(data);
// 3. 返回完整HTML
return html;
}
- HTML 生成位置
虽然数据来自外部 API,但 HTML 的生成是在 Nuxt 服务器上完成的,而不是在浏览器中完成的
5. Nuxt 中的数据获取方式
- asyncData 方法
export default {
async asyncData({ $axios }) {
// 服务器端执行
const data = await $axios.$get("/api/data");
return { data };
},
};
- fetch 钩子
export default {
async fetch() {
// 服务器端执行
this.items = await this.$axios.$get("/api/items");
},
};
6. 验证是否 SSR 的方法
export default {
mounted() {
// 查看HTML源代码是否包含数据
console.log(document.documentElement.outerHTML);
// 或使用Vue开发者工具查看
console.log("是否为SSR:", this.$nuxt?.$ssrContext != null);
},
};
7. SSR 的优化建议
- 使用缓存
// nuxt.config.js
export default {
render: {
http2: {
push: true,
},
static: {
maxAge: 1000 * 60 * 60 * 24 * 7, // 7天缓存
},
},
};
- 数据预取优化
export default {
async asyncData({ $axios, store }) {
try {
// 并行请求多个API
const [products, categories] = await Promise.all([
$axios.$get("/api/products"),
$axios.$get("/api/categories"),
]);
return { products, categories };
} catch (error) {
console.error(error);
return { error: "Failed to load data" };
}
},
};
记住:SSR 的关键不在于数据从哪里来,而在于HTML 的生成位置和时机。只要 HTML 是在服务器端生成的,并且包含了完整的内容,就是 SSR。
references:
服务端渲染(SSR)和预渲染(Prerendering)有什么区别?
官网网站用什么技术栈比较合理? - 知乎 (zhihu.com)
2022 年,企业官网开发,前端需要用到那些技术? - 知乎 (zhihu.com)
nuxt 框架 Universal 和 Spa 两种 render mode 的区别_daruanzai3165 的博客-CSDN 博客
SSG 和 SSR 有什么区别? - 知乎 (zhihu.com)
https://zhuanlan.zhihu.com/p/431930680
chrisvfritz/prerender-spa-plugin: Prerenders static HTML in a single-page application. (github.com)
Next.js 的三种渲染方式(BSR、SSG、SSR) - 知乎 (zhihu.com)
vue 单页面(SPA)和多页面(MPA)的区别(详细答案) - 掘金 (juejin.cn)
浅谈 SPA、CSR、SSR、MPA、SSG 专有名词 | 是 Ray 不是 Array (israynotarray.com)
vue 服务端渲染和客户端渲染的区别是什么-前端问答-PHP 中文网
为什么客户端渲染首屏渲染慢和 SEO 问题_Coder 小何的博客-CSDN 博客
Render
js 在任何时候都会阻塞 dom,所以通常会将 js 放在 body 结束之后,这样页面渲染完成再执行 js,有利于用户体验
css 的加载会阻塞后面 js 的执行;css 加载不会阻塞 DOM 树的解析,但会阻塞 Render 树的渲染
重排一定会触发重绘,重绘不一定会触发重排
触发重排
- 页面首次渲染
- 浏览器窗口大小变化
- 元素尺寸、位置、内容、字体大小发生变化
- 添加或删除可见的元素
- 激活伪类时
触发重绘
- 改变元素颜色、背景、visibility、outline 等属性
referrences
*浏览器已原生支持 ES 模块,这对前端开发来说意味着什么?
客户端渲染路线:
- 请求一个 html
- 服务端返回一个 html
- 浏览器下载 html 中的 js/css 文件
- 等待 js 文件下载完成,加载并初始化完成,可以运行,由 js 代码向后端请求数据(ajax/fetch)
- 等待后端数据返回
- 客户端从无到完整地,把数据渲染为响应页面
服务端渲染路线:
- 请求一个 html
- 服务端请求数据(内网请求快)
- 服务器初始渲染(服务端性能好,较快),可以理解为组装数据和模板
- 服务端返回已经有正确内容的页面
- 客户端请求 js/css 文件
- 等待 js 文件下载完成,加载并初始化完成
- 客户端把剩下一部分渲染完成(内容小,渲染快)
Deploy
# ubuntu install nginx
sudo apt install -y nginx
直接在浏览器访问服务器公网ip,如果出现nginx欢迎页面,说明安装成功