MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript中的服务端渲染技术探索

2022-11-211.6k 阅读

什么是服务端渲染(SSR)

服务端渲染,简单来说,就是在服务器端生成完整的 HTML 页面,然后将这个 HTML 页面发送到客户端。与传统的客户端渲染(CSR)不同,CSR 是先将一个空的 HTML 页面发送到客户端,然后客户端通过 JavaScript 代码获取数据并渲染页面。

服务端渲染的优势在于首屏加载速度更快。因为服务器已经生成了完整的 HTML,用户无需等待 JavaScript 加载和执行就可以看到页面内容,这对于提高用户体验非常重要,尤其是在网络环境不佳的情况下。另外,SSR 对于搜索引擎优化(SEO)也更友好,因为搜索引擎爬虫通常不会执行 JavaScript,而 SSR 生成的 HTML 页面包含了实际的内容,更容易被搜索引擎抓取和索引。

JavaScript 中的服务端渲染技术栈

Node.js

在 JavaScript 领域,Node.js 是实现服务端渲染的基础平台。Node.js 基于 Chrome 的 V8 引擎,提供了一个事件驱动、非阻塞 I/O 的运行环境,使得 JavaScript 可以在服务器端高效运行。

Express.js

Express.js 是基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供了一系列强大的特性来帮助开发者创建各种 web 应用。在服务端渲染中,Express.js 常被用于搭建服务器,处理路由和中间件等功能。

React 与 Next.js

React 是一个用于构建用户界面的 JavaScript 库。它采用虚拟 DOM 等技术,使得页面的更新高效且可预测。在服务端渲染方面,React 提供了 react-dom/server 模块,其中的 renderToString 等方法可以将 React 组件渲染为字符串形式的 HTML。

Next.js 是一个基于 React 的服务端渲染框架,它简化了 React 应用的服务端渲染流程。Next.js 提供了自动代码拆分、静态文件服务、路由等功能,使得开发者可以更轻松地构建服务端渲染的 React 应用。

Vue 与 Nuxt.js

Vue 是一款渐进式 JavaScript 框架,用于构建用户界面。Vue 也支持服务端渲染,通过 vue - server - renderer 模块可以将 Vue 组件渲染为 HTML 字符串。

Nuxt.js 是基于 Vue.js 的服务端渲染框架,它和 Next.js 类似,为 Vue 应用的服务端渲染提供了许多便捷的功能,如自动路由、布局系统等。

基于 Node.js 和 Express.js 的简单服务端渲染示例

下面我们通过一个简单的示例来展示如何使用 Node.js 和 Express.js 进行基本的服务端渲染。

首先,确保你已经安装了 Node.js。然后创建一个新的项目目录,并在该目录下初始化 package.json 文件:

mkdir ssr - example
cd ssr - example
npm init -y

接着安装 Express.js:

npm install express

创建一个 server.js 文件,内容如下:

const express = require('express');
const app = express();
const port = 3000;

// 模拟数据
const data = {
  message: 'Hello, Server - Side Rendering!'
};

// 渲染函数,这里简单返回包含数据的 HTML 字符串
function renderHTML(data) {
  return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF - 8">
      <meta name="viewport" content="width=device - width, initial - scale = 1.0">
      <title>SSR Example</title>
    </head>
    <body>
      <h1>${data.message}</h1>
    </body>
    </html>
  `;
}

app.get('/', (req, res) => {
  const html = renderHTML(data);
  res.send(html);
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在这个示例中,我们创建了一个 Express 服务器。renderHTML 函数将模拟数据嵌入到 HTML 模板中,当客户端访问根路径时,服务器返回渲染好的 HTML 页面。运行 node server.js,然后在浏览器中访问 http://localhost:3000,就可以看到渲染后的页面。

React 服务端渲染深入剖析

React 服务端渲染原理

React 的服务端渲染主要依赖 react - dom/server 模块。renderToString 方法会将 React 组件递归地渲染为一个 HTML 字符串。在渲染过程中,React 会创建一个虚拟 DOM 树,但是与客户端渲染不同的是,它不会将虚拟 DOM 树挂载到真实 DOM 上,而是直接生成 HTML 字符串。

例如,假设有一个简单的 React 组件 Hello.js

import React from'react';

const Hello = () => {
  return <div>Hello, React SSR!</div>;
};

export default Hello;

在服务端,可以这样将其渲染为 HTML 字符串:

import React from'react';
import { renderToString } from'react - dom/server';
import Hello from './Hello';

const html = renderToString(<Hello />);
console.log(html);
// 输出: <div>Hello, React SSR!</div>

使用 Next.js 进行 React 服务端渲染

  1. 创建 Next.js 项目 首先,通过 npx create - next - app 命令创建一个新的 Next.js 项目:
npx create - next - app my - next - app
cd my - next - app
  1. 页面渲染 Next.js 约定页面组件存放在 pages 目录下。例如,创建一个 pages/index.js 文件:
import React from'react';

const HomePage = () => {
  return (
    <div>
      <h1>Welcome to Next.js SSR</h1>
    </div>
  );
};

export default HomePage;

Next.js 会自动为这个页面生成路由,当运行 npm run dev 并访问 http://localhost:3000 时,就可以看到渲染后的页面。 3. 数据获取 Next.js 提供了 getStaticPropsgetServerSideProps 方法来进行数据获取。getStaticProps 用于在构建时获取数据,适用于数据不经常变化的情况。getServerSideProps 则在每次请求时获取数据,适用于数据实时性要求较高的场景。

例如,使用 getServerSideProps 获取数据并传递给页面组件:

import React from'react';

const HomePage = ({ data }) => {
  return (
    <div>
      <h1>{data.message}</h1>
    </div>
  );
};

export async function getServerSideProps() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return {
    props: {
      data
    }
  };
}

export default HomePage;

Vue 服务端渲染深入剖析

Vue 服务端渲染原理

Vue 的服务端渲染通过 vue - server - renderer 实现。它同样会将 Vue 组件渲染为 HTML 字符串。在渲染过程中,Vue 会遍历组件树,生成相应的 HTML 结构。

假设有一个简单的 Vue 组件 Hello.vue

<template>
  <div>Hello, Vue SSR!</div>
</template>

<script>
export default {
  name: 'Hello'
};
</script>

在服务端可以这样渲染:

const Vue = require('vue');
const { createRenderer } = require('vue - server - renderer');

const app = new Vue({
  template: `<Hello />`,
  components: {
    Hello: require('./Hello.vue')
  }
});

const renderer = createRenderer();

renderer.renderToString(app, (err, html) => {
  if (err) {
    console.error(err);
  } else {
    console.log(html);
    // 输出: <div>Hello, Vue SSR!</div>
  }
});

使用 Nuxt.js 进行 Vue 服务端渲染

  1. 创建 Nuxt.js 项目 使用 npx create - nuxt - app 命令创建一个新的 Nuxt.js 项目:
npx create - nuxt - app my - nuxt - app
cd my - nuxt - app
  1. 页面渲染 Nuxt.js 的页面组件存放在 pages 目录下。例如,创建一个 pages/index.vue 文件:
<template>
  <div>
    <h1>Welcome to Nuxt.js SSR</h1>
  </div>
</template>

<script>
export default {
  name: 'IndexPage'
};
</script>

运行 npm run dev 并访问 http://localhost:3000,即可看到渲染后的页面。 3. 数据获取 Nuxt.js 提供了 asyncData 方法来获取数据。asyncData 会在组件每次加载之前被调用,可以在其中进行异步数据请求,并将数据传递给组件。

例如:

<template>
  <div>
    <h1>{{ data.message }}</h1>
  </div>
</template>

<script>
export default {
  async asyncData() {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return { data };
  }
};
</script>

服务端渲染中的同构(Isomorphic)与 Hydration

同构的概念

同构是指在服务端和客户端运行相同的 JavaScript 代码。在服务端渲染中,同构代码可以确保在服务端生成的 HTML 与客户端渲染的结果一致。这意味着一些逻辑,如数据获取、状态管理等代码可以在服务端和客户端复用。

例如,在 React 应用中,可以将数据获取逻辑封装在一个函数中,然后在 getServerSideProps(服务端)和 useEffect(客户端)中调用这个函数。

Hydration 的概念与实现

Hydration 即水合,是指在服务端渲染生成 HTML 发送到客户端后,客户端通过 JavaScript 代码将静态的 HTML 转变为可交互的应用的过程。

在 React 中,客户端使用 ReactDOM.hydrate 方法来进行 Hydration。例如:

import React from'react';
import ReactDOM from'react - dom';
import App from './App';

ReactDOM.hydrate(<App />, document.getElementById('root'));

在 Vue 中,客户端通过挂载 Vue 实例来实现 Hydration:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF - 8">
  <meta name="viewport" content="width=device - width, initial - scale = 1.0">
  <title>Vue SSR Hydration</title>
</head>
<body>
  <!-- 服务端渲染生成的 HTML 内容 -->
  <div id="app">{{ message }}</div>
  <script src="/client.js"></script>
</body>
</html>

client.js 中:

import Vue from 'vue';
import App from './App.vue';

new Vue({
  render: h => h(App)
}).$mount('#app');

服务端渲染的性能优化

缓存策略

  1. 页面缓存 在服务端,可以对渲染后的页面进行缓存。例如,使用内存缓存(如 node - cache 库)或分布式缓存(如 Redis)。如果相同的请求再次到来,且缓存未过期,则直接返回缓存中的页面,避免重复渲染。

node - cache 为例:

const NodeCache = require('node - cache');
const myCache = new NodeCache();

app.get('/', async (req, res) => {
  const cached = myCache.get('home - page');
  if (cached) {
    return res.send(cached);
  }

  const html = await renderHTML();
  myCache.set('home - page', html);
  res.send(html);
});
  1. 数据缓存 对于通过 API 获取的数据,也可以进行缓存。这样在下次请求时,如果数据未过期,就不需要再次请求 API,从而提高响应速度。

代码拆分与懒加载

  1. React 中的代码拆分 在 React 中,可以使用动态 import() 进行代码拆分。Next.js 也自动支持代码拆分,它会将页面组件和相关依赖进行拆分,只在需要时加载。例如:
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function App() {
  return (
    <div>
      <React.Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </React.Suspense>
    </div>
  );
}
  1. Vue 中的代码拆分与懒加载 Vue 可以通过 import() 实现组件的懒加载。在 Nuxt.js 中,也支持自动代码拆分。例如:
<template>
  <div>
    <component :is="LazyComponent" />
  </div>
</template>

<script>
export default {
  components: {
    LazyComponent: () => import('./LazyComponent.vue')
  }
};
</script>

优化渲染性能

  1. 减少不必要的渲染 在 React 中,可以使用 React.memo 来包裹函数组件,防止其在 props 未变化时重新渲染。在 Vue 中,可以通过 shouldUpdate 生命周期钩子来控制组件是否更新。

例如,在 React 中:

const MyComponent = React.memo((props) => {
  return <div>{props.value}</div>;
});
  1. 优化 DOM 操作 尽量减少直接操作 DOM 的次数,在 React 和 Vue 中,虚拟 DOM 机制已经帮助我们优化了 DOM 操作。但是在某些情况下,如自定义指令(Vue)或 ref(React)操作 DOM 时,要确保操作的高效性。

服务端渲染面临的挑战与解决方案

内存与资源消耗

服务端渲染需要在服务器端执行 JavaScript 代码,这可能会消耗较多的内存和 CPU 资源。尤其是在高并发情况下,每个请求都可能触发渲染过程,导致服务器负载过高。

解决方案可以是采用负载均衡,将请求分发到多个服务器实例上,减轻单个服务器的压力。另外,合理设置缓存策略,减少重复渲染,也可以降低资源消耗。

状态管理问题

在服务端渲染中,状态管理需要特别注意。因为服务端渲染是无状态的,每次请求都会重新渲染。如果在服务端和客户端共享状态,需要确保状态的一致性。

例如,在 React 中使用 Redux 进行状态管理时,可以在服务端将初始状态序列化并嵌入到 HTML 中,然后在客户端重新创建 Redux 存储时,使用这个初始状态。

部署与维护

服务端渲染应用的部署相对复杂,需要考虑服务器环境、静态资源管理等问题。而且由于代码在服务端和客户端运行,调试也变得更加困难。

对于部署,可以使用容器化技术(如 Docker)来封装应用及其依赖,便于在不同环境中部署。在调试方面,可以使用日志记录、调试工具(如 Node.js 的调试器)来定位问题。

通过以上对 JavaScript 中服务端渲染技术的探索,我们深入了解了其原理、技术栈、实现方式、优化方法以及面临的挑战与解决方案。服务端渲染可以显著提升应用的性能和用户体验,但在实际应用中需要根据项目需求和场景合理选择和使用。