Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >React服务端渲染与同构实践

React服务端渲染与同构实践

作者头像
IMWeb前端团队
发布于 2019-12-04 08:43:27
发布于 2019-12-04 08:43:27
1.1K00
代码可运行
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队
运行总次数:0
代码可运行

本文作者:IMWeb IMWeb团队 原文出处:IMWeb社区 未经同意,禁止转载

前两年服务端渲染和同构的概念火遍了整个前端界,几乎所有关于前端的分享会议都有提到。在这年头,无论你选择什么技术栈,不会做个服务端渲染可能真的快混不下去了!最近刚好实现了个基于 React&Redux 的同构直出应用,赶紧写个文章总结总结压压惊。

前言

在了解实践过程之前,让我们先明白几个概念(非新手可直接跳过)。

什么是服务端渲染(Server-Side Rendering)

服务端渲染,又可以叫做后端渲染或直出。

早些年前,大部分网站都使用传统的 MVC 架构进行后端渲染,就是实现一个 Controller,处理请求时在服务端拉取到数据 Model,使用模版引擎结合 View 渲染出页面,比如 Java + Velocity、PHP 等。但随着前端脚本 JS 的发展,拥有更强大的交互能力后,前后端分离的概念被提出,也就是拉取数据和渲染的操作由前端来完成。

关于前端渲染还是后端渲染之争,可以看文章后面的参考链接,这里不做讨论。这里照搬后端渲染的优势:

  • 更好的首屏性能,不需要提前先下载一堆 CSS 和 JS 后才看到页面
  • 更利于 SEO,蜘蛛可以直接抓取已渲染的内容

什么是同构应用(Isomorphic)

同构,在本文特指服务端和客户端的同构,意思是服务端和客户端都可以运行的同一套代码程序。

SSR 同构也是在 Node 这门服务端语言兴起后,使得 JS 可以同时运行在服务端和浏览器,使得同构的价值大大提升:

  • 提高代码复用率
  • 提高代码可维护性

基于 React&Redux 的考虑

其实 Vue 和 React 都提供了 SSR 相关的能力,在决定在做之前我们考虑了一下使用哪种技术栈,之所以决定使用 React 是因为对于团队来说,统一技术栈在可维护性上显得比较重要:

  • 已有一套基于 React 的 UI
  • 已有基于 React&Redux 的脚手架
  • 已在 React 直出上有一定的实践经验(仅限于组件同构,Controller 并不通用)

React 提供了一套将 Virtual DOM 输出为 HTML 文本的API

Redux 提供了一套将 reducers 同构复用的解决方案

方案与实践

首先先用脚手架生成了基于 React&Redux 的异步工程目录:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- dist/ # 构建结果
    - xxx.html
    - xxx_[md5].js
    - xxx_[md5].css
- src/ # 源码入口
    - assets/
        - css/ # 全局CSS
        - template.html # 页面模版
    - pages/ # 页面源码目录
        - actions.js # 全局actions
        - reducers.js # 全局reducers
        - xxx/ # 页面名称目录
            - components/ # 页面级别组件
            - index.jsx # 页面主入口
            - reducers.js # 页面reducers
            - actions.js # 页面actions
    - components/ # 全局级别组件
- webpack.config.js
- package.json
- ...

可以看到,现有的异步工程,构建会使用web-webpack-plugin将所有src/pages/xxx/index.js当做入口为每个页面编译出异步 html、js 和 css 文件。

1. 添加 Node Server

既然要做直出,首先需要一个 Web Server 吧,可以使用Koa,这里我们采用了团队自研基于KoaIMServer(作者是开源工具whistle的作者,用过whistle的我表示已经离不开它了),Server 工程目录如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- server/
    - app/
        - controller/ # controllers
            - indexReact.js # 通用React直出Controller
        - middleware/ # 中间件
        - router.js   # 路由设置
    - config/
        - config.js # 项目配置
    - lib/ # 内部依赖库
    - dispatch.js # 启动入口
    - package.json
    - ...

由于是一个多页面应用(非 SPA),上文提到之前团队的实践中 Controller 逻辑并不是通用的,也就是说只要业务需求新增一个页面那么就得手写多一个 Controller,而且这些 Controllers 都存在共性逻辑,每个请求过来都要经历:

  1. 根据页面 reducer 创建 store
  2. 拉取首屏数据
  3. 渲染结果
  4. ...(其他自定义钩子)

那我们为什么不实现一个通用的 Controller 将这些逻辑都同构了呢:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// server/app/controller/indexReact.js
const react = require('react');
const { renderToString } = require('react-dom/server');
const { createStore, applyMiddleware } = require('redux');
const thunkMiddleware = require('redux-thunk').default;
const { Provider } = require('react-redux');

async function process(ctx) {
  // 创建store
  const store = createStore(
    reducer /* 1.同构的reducer */,
    undefined,
    applyMiddleware(thunkMiddleware)
  );

  // 拉取首屏数据
  /* 2.同构的component静态方法getPreloadState */
  const preloadedState = await component.getPreloadState(store).then(() => {
    return store.getState();
  });

  // 渲染html
  /* 2.同构的component静态方法getHeadFragment */
  const headEl = component.getHeadFragment(store);
  const contentEl = react.createElement(
    Provider,
    { store },
    react.createElement(component)
  );
  ctx.type = 'html';
  /* 3.基于页面html编译的模版函数template */
  ctx.body = template({
    preloadedState,
    head: renderToString(headEl),
    html: renderToString(contentEl)
  });
}

module.exports = process;

上述代码相当于将处理过程钩子化了,只要同构代码提供相应的钩子即可。

当然,还得根据页面生成相应的路由

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// server/app/router.js
const config = require('../config/config');
const indexReact = require('./controler/indexReact');

module.exports = app => {
  // 需要直出页面路由配置
  const { routes } = config;

  // IMServer会调用此方法,传入koa-router实例
  return router => {
    Object.entries(routes).forEach(([name, v]) => {
      const { pattern } = v;

      router.get(
        name, // 目录名称xxx
        pattern, // 目录路由配置,比如'/course/:id'
        indexReact
      );
    });
  };
};

至此服务端代码已基本完成。

2. 同构构建打通

上一步服务端代码依赖了几份同构代码。

  • 页面数据纯函数 reducer.js
  • 页面组件主入口 component.js
  • 基于web-webpack-plugin生成的页面 xxx.html 再编译的模版函数 template

我选择了通过构建编译出这些文件,而不是在服务端引入babel-register来直接引入前端代码,是因为我想保留更高的自由度,即构建可以做更多babel-register做不了的事情。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// webpack-ssr.config.js
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const write= require('write');
const webpack = require('webpack');
const FilterPlugin = require('filter-chunk-webpack-plugin');
const { rootDir, serverDir, resolve } = require('./webpack-common.config');
const ssrConf = require('./server/ssr.config');

const { IgnorePlugin } = webpack;

const componentsEntry = {};
const reducersEntry = {};
glob.sync('src/pages/*/').forEach(dirpath => {
  const dirname = path.basename(dirpath);
  const options = { realpath: true };
  componentsEntry[dirname] = glob.sync(
    `${dirpath}/isomorph.{tsx,ts,jsx,js}`,
    options
  )[0];
  reducersEntry[dirname] = glob.sync(
    `${dirpath}/reducers.{tsx,ts,jsx,js}`,
    options
  )[0];
});
const ssrOutputConfig = (o, dirname) => {
  return Object.assign({}, o, {
    path: path.resolve(serverDir, dirname),
    filename: '[name].js',
    libraryTarget: 'commonjs2'
  });
};
const ssrExternals = [/assets\/lib/];
const ssrModuleConfig = {
  rules: [
    {
      test: /\.(css|scss)$/,
      loader: 'ignore-loader'
    },
    {
      test: /\.jsx?$/,
      loader: 'babel-loader?cacheDirectory',
      include: [
        path.resolve(rootDir, 'src'),
        path.resolve(rootDir, 'node_modules/@tencent')
      ]
    },
    {
      test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf)$/,
      loader: 'file-loader'
    }
  ]
};

const ssrPages = Object.entries(ssrConf.routes).map(([pagename]) => {
  return `${pagename}.js`;
});

const ssrPlugins = [
  new IgnorePlugin(/^\.\/locale$/, /moment$/),
  new FilterPlugin({
    select: true,
    patterns: ssrPages
  })
];

const ssrTemplatesDeployer = assets => {
  Object.entries(assets).forEach(([name, asset]) => {
    const { source } = asset;

    // ssr template
    if (/.html$/.test(name)) {
      const content = source()
        // eslint-disable-next-line
        .replace(/(<head[^>]*>)/, '$1${head}')
        .replace(
          /(<\/head>)/,
          // eslint-disable-next-line
          "<script>window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}</script>$1"
        )
        .replace(/(<div[^>]*id="react-body"[^>]*>)/, '$1${html}'); // eslint-disable-line

      write.sync(path.join(serverDir, 'templates', name), content);
    }
  });
};
const devtool = 'source-map';

function getSSRConfigs(options) {
  const { mode, output } = options;

  return [
    {
      mode,
      entry: componentsEntry,
      output: ssrOutputConfig(output, 'components'),
      resolve,
      devtool,
      target: 'node',
      externals: ssrExternals,
      module: ssrModuleConfig,
      plugins: ssrPlugins
    },
    {
      mode,
      entry: reducersEntry,
      output: ssrOutputConfig(output, 'reducers'),
      resolve,
      devtool,
      target: 'node',
      externals: ssrExternals,
      module: ssrModuleConfig,
      plugins: ssrPlugins
    }
  ];
}

module.exports = {
  ssrTemplatesDeployer,
  getSSRConfigs
};

上述代码将 Controller 需要的同构模块和文件打包到了 server/目录下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
src/
    - pages/
        - xxx
            - template.html # 页面模版
            - reducers.js # 页面reducer入口
            - isomorph.jsx # 页面服务端主入口
server/
    - components/
        - xxx.js
    - reducers/
        - xxx.js
    - templates
        - xxx.html # 在Node读取并编译成模版函数即可

3. 实现同构钩子

还需要在同构模块中实现通用 Controller 约定。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/pages/xxx/isomorph.tsx
import * as React from 'react';
import { bindActionCreators, Store } from 'redux';
import * as actions from './actions';
import { AppState } from './reducers';
import Container, { getCourceId } from './components/Container';

Object.assign(Container, {
  getPreloadState(store: Store<AppState>) {
    type ActionCreatorsMap = {
      fetchCourseInfo: (x: actions.CourseInfoParams) => Promise<any>;
    };

    const cid = getCourceId();
    const { fetchCourseInfo } = bindActionCreators<{}, ActionCreatorsMap>(actions, store.dispatch);

    return fetchCourseInfo({ course_id: cid })
  },

  getHeadFragment(store: Store<AppState>) {
    const cid = getCourceId();
    const { courseInfo } = store.getState();
    const { name, summary, agency_name: agencyName } = courseInfo.data;
    const keywords = ['腾讯课堂', name, agencyName].join(',');
    const canonical = `//ke.qq.com/course/${cid}`;

    return (
      <>
        <title>{name}</title>
        <meta name="keywords" content={keywords} />
        <meta name="description" itemProp="description" content={summary} />
        <link rel="canonical" href={canonical} />
      </>
    );
  },
});

export default Container;

至此同构已基本打通。

4. 异步入口&容灾

剩下来就好办了,在异步 JS 入口中使用ReactDOM.hydrate

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/pages/xxx/index.tsx
import * as React from 'react';
import { hydrate } from 'react-dom';
import { applyMiddleware, compose, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import reducers from './reducers';
import Container from './components/Container';
import './index.css';

let store;
const preloadState = window.__PRELOADED_STATE__;

if (process.env.NODE_ENV === 'production') {
  store = createStore(reducers, preloadState, applyMiddleware(thunkMiddleware));
} else {
  store = createStore(
    reducers,
    preloadState,
    compose(
      applyMiddleware(thunkMiddleware),
      window.devToolsExtension ? window.devToolsExtension() : (f: any) => f
    )
  );
}

hydrate(
  <Provider store={store}>
    <Container />
  </Provider>,
  window.document.getElementById('react-body')
);

hydrate() Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup. React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them.

容灾是指当服务端因为某些原因挂掉的时候,由于我们还有构建生成 xxx.html 异步页面,可以在 nginx 层上做一个容灾方案,当上层 Svr 出现错误时,降级异步页面。

踩坑

  • 无法同构的业务逻辑

像因为生命周期的不同要在componentDidMount绑定事件,不能在服务端能执行到的地方访问 DOM API 这些大家都应该很清楚了,其实大概只需要实现最主要几个同构的基础模块即可:

  1. 访问 location 模块
  2. 访问 cookie模块
  3. 访问 userAgent 模块
  4. request 请求模块
  5. localStorage、window.name 这种只能降级处理的模块(尽量避免在首屏逻辑使用到它们)

当然我要说的还有一些依赖客户端能力的模块,比如 wx 的 sdk,qq 的 sdk 等等。

这里稍微要提一下的是,我最初设计的时候想尽可能不破坏团队现有的编码习惯,像 location、cookie之类的这些模块方法在每次请求过来的时候,拿到的值应该是不一样的,如何实现这一点是参考 TSW 的做法:https://tswjs.org/doc/api/global,Node 的domain 模块使得这类设计成为可能。

但是依旧要避免模块局部变量的写法(有关这部分内容,我另写了一篇文章可做参考

  • 使用ignore-loader忽略掉依赖的 css 文件
  • core-js包导致内存泄漏
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    {
      test: /\.jsx?$/,
      loader: 'babel-loader?cacheDirectory',
      // 干掉babel-runtime,其依赖core-js源码对global['__core-js_shared__']操作引起内存泄漏
      options: {
        babelrc: false,
        presets: [
          ['env', {
            targets: {
              node: true
            }
          }],
          'stage-2',
          'react'
        ],
        plugins: ['syntax-dynamic-import']
      },
      include: [
        path.resolve(rootDir, 'src'),
        path.resolve(rootDir, 'node_modules/@tencent')
      ]
    }

这部分 core-js 的上的 issue 也有说明为什么要这么做:

https://github.com/babel/babel-loader/issues/152

其实在 node 上 es6 的特性是都支持了的,打包出的同构模块需要尽可能的精简。

后续思考

  • 可以看齐 Nextjs

这整个设计其实把构建能力抽象出来,钩子可配置化后,就可以成为一个直出框架了。当然也可以像 Nextjs 那样实现一些 Document等组件来使用。

  • 发布的不便利性

当前设计由于 Server 的代码依赖了构建出来的同构模块,在日常开发中,前端做一些页面修改是经常发生的事,比如修改一些事件监听,而这时候因为 js, css 资源 MD5 值的变化,导致 template.html 变化,故而导致 server 包需要发布,如果业务有有多节点,都要一一无损重启。肯定是有办法做到发布代码而不用重启 Node 服务的。

  • 性能问题(TODO)

以上就是本文的所有内容,请多多指教,欢迎交流(文中代码基本都是经过删减的)~

参考资料:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-07-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
React项目的服务端渲染改造(koa2+webpack3.11)
选型思路:实现服务端渲染,想用React最新的版本,并且不对现有的写法做大的改动,如果一开始就打算服务端渲染,建议直接用NEXT框架来写 项目地址:https://github.com/wlx200510/react_koa_ssr 脚手架选型:webpack3.11.0 + react Router4 + Redux + koa2 + React16 + Node8.x 主要心得:对React的相关知识更加熟悉,成功拓展自己的技术领域,对服务端技术在实际项目上有所积累 注意点:使用框架前一定确认当前web
卡少
2018/05/16
1.3K0
从头开始,彻底理解服务端渲染原理
大家好,我是神三元,这一次,让我们来以React为例,把服务端渲染(Server Side Render,简称“SSR”)学个明明白白。
Nealyang
2019/09/29
2.3K1
从头开始,彻底理解服务端渲染原理
教你如何在React及Redux项目中进行服务端渲染
使用 redux-saga 处理异步action,使用 express 处理页面渲染
书童小二
2018/09/03
3K0
教你如何在React及Redux项目中进行服务端渲染
面试官:说说React-SSR的原理
所谓同构,简而言之就是,第一次访问后台服务时,后台直接把前端要显示的界面全部返回,而不是像 SPA 项目只渲染一个 <div id="root"></div> 剩下的都是靠 JavaScript 脚本去加载。这样一来可以大大减少首屏等待时间。
beifeng1996
2022/10/10
2.2K0
IMVC(同构 MVC)的前端实践
导语 随着 Backbone 等老牌框架的逐渐衰退,前端 MVC 发展缓慢,有逐渐被 MVVM/Flux 所取代的趋势。 然而,纵观近几年的发展,可以发现一点,React/Vue 和 Redux/Vuex 是分别在 MVC 中的 View 层和 Model 层做了进一步发展。如果 MVC 中的 Controller 层也推进一步,将得到一种升级版的 MVC,我们称之为 IMVC(同构 MVC)。 IMVC 可以实现一份代码在服务端和浏览器端皆可运行,具备单页应用和多页应用的所有优势,并且可以这两种模式里通过
CSDN技术头条
2018/02/12
1.3K0
IMVC(同构 MVC)的前端实践
react 同构初步(1)
单页面应用(SPA)在传统的实现)上,面临着首页白屏加载时间过长,seo难以优化的难题。解决这个问题的思路之一就是ssr(服务端渲染)。
一粒小麦
2019/12/19
1.6K2
我的React服务端渲染实践
最近这段时间因为工作需要,实践了一下服务端渲染(Server Side Render,以下简称 SSR)技术,在这个过程中遇到了很多问题,也参考了很多开源框架的解决方案,感觉受益匪浅,于是有了这篇文章,目的是从零开始,教会大家如何搭建一个属于自己的基于 React的 SSR 框架,彻底弄明白SSR的原理。
astonishqft
2022/05/10
2.1K0
我的React服务端渲染实践
手把手教你全家桶之React(二)
前言 上一篇已经讲了一些react的基本配置,本遍接着讲热更新以及react+redux的配置与使用。 热更新 我们在实际开发时,都有用到热更新,在修改代码后,不用每次都重启服务,而是自动更新。并而不
用户2145235
2018/05/18
1.7K0
【redux】详解react/redux的服务端渲染:页面性能与SEO
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载好,这较大程
啦啦啦321
2018/01/03
1.5K0
【redux】详解react/redux的服务端渲染:页面性能与SEO
webpack4 中的 React 全家桶配置指南,实战!
点赞再看,养成习惯本文 GitHub https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。
前端小智@大迁世界
2022/06/15
1.9K0
webpack4 中的 React 全家桶配置指南,实战!
react 同构初步(4)
用代理规避跨域其实是很简单的事情,在往期的文章中已经有过类似的案例。但现在需要用"中台"的角度去思考问题。当前的项目分为三大部分:客户端(浏览器),同构服务端(nodejs中台,端口9000)和负责纯粹后端逻辑的后端(mockjs,端口9001)。
一粒小麦
2020/01/02
1.9K0
腾讯新闻React同构直出优化实践
为什么做直出 就是为了“性能”!!! 按照经验来说,直出,能够减少20% - 50%不等的首屏时间,因此尽管增加一定维护成本,前端们还是前赴后继地在搞直出。 除此之外,有些特定的业务做直出能够弥补
李成熙heyli
2018/01/05
2.2K0
腾讯新闻React同构直出优化实践
【长文慎入】一文吃透React SSR服务端同构渲染
前段时间一直在研究 react ssr技术,然后写了一个完整的 ssr开发骨架。今天写文,主要是把我的研究成果的精华内容整理落地,另外通过再次梳理希望发现更多优化的地方,也希望可以让更多的人少踩一些坑,让更多的人理解和掌握这个技术。
ConardLi
2019/09/25
4K0
【长文慎入】一文吃透React SSR服务端同构渲染
手把手带你用next搭建一个完善的react服务端渲染项目(集成antd、redux、样式解决方案)
本文参考了慕课网jokcy老师的React16.8+Next.js+Koa2开发Github全栈项目,也算是做个笔记吧。
ssh_晨曦时梦见兮
2020/04/10
5.8K0
手把手教你全家桶之React(三)--完结篇
前言 本篇主要是讲一些全家桶的优化与完善,基础功能上一篇已经讲得差不多了。直接开始: Source Maps 当javaScript抛出异常时,我们会很想知道它发生在哪个文件的哪一行。但是webpack 总是将文件输出为一个或多个bundle,我们对错误的追踪很不方便。Source maps试图解决这一个问题,我们只需要改变一下配置项即可。 在webpack.dev.config.js中加入: devtool:"inline-source-map" css编译 这里以less-loader为例,先安装 l
用户2145235
2018/06/26
1.1K0
react 同构初步(2)
现在已经有了三条指令,做项目时,必须启动三个窗口,给开发带来了不便。npm上的开源库concurrently把它们整合为一条命令,可以提升开发体验。
一粒小麦
2019/12/19
2.1K2
react脚手架改造(react/react-router/redux/eslint/karam/immutable/es6/webpack/Redux DevTools)
公司突然组织需要重新搭建一个基于node的论坛系统,前端采用react,上网找了一些脚手架,或多或少不能满足自己的需求,最终在基于YeoMan的react脚手架generator-react-webpack上搭建改造,这里作为记录。 代码在这里:github 另外推荐地址:react-starter-kit 简单文件夹结构 ├── README.md # 项目README文件 ├── conf # 配置文件夹
糊糊糊糊糊了
2018/05/09
1.7K0
react脚手架改造(react/react-router/redux/eslint/karam/immutable/es6/webpack/Redux DevTools)
使用React做同构应用
React是用于开发数据不断变化的大型应用程序的前端view框架,结合其他轮子例如redux和react-router就可以开发大型的前端应用。
frontoldman
2019/09/02
1K0
如何优化你的超大型React应用
新,会自上而下逐渐刷新整个子孙组件,这样性能损耗重复渲染就会多出很多,所以我们不仅要单一数据来源控制组件刷新,偶尔还需要在shouldComponentUpdate中对比nextProps和this.props 以及this.state以及nextState.
Peter谭金杰
2019/08/02
2.1K0
SSR React同构渲染改造
SSR(Server Side Rendering),顾名思义英文单词翻译过来就是服务端渲染,约在十年前左右,服务端渲染主要是由后端人员来主持改造,前端提供页面模板,后端在模板中填充页面相关的数据然后直接以整个html的形式返回给用户浏览器进行展示,由于在填充数据时已经将原有javascript的功能直接在后端实现,所以在服务器性能比较稳定的前提下,用户侧可以很快看到整个完整页面加载出来,使用体验很好,加之搜索引擎都是基于爬虫来进行收录,服务端渲染对于SEO会有非常好的效果。
lealc
2024/01/12
5460
SSR React同构渲染改造
相关推荐
React项目的服务端渲染改造(koa2+webpack3.11)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验