深入学习SSR , NextJS 一起重构 掘金!

6,054 阅读7分钟

前言

逃不开的SSR,大掘金就是使用Nuxt的SSR,本篇文章对SPA单页面应用的的一些SEO痛点进行总结,再简单实现一下掘金PC界面,学习一下Next,冲!

所以

一位程序员的职业生涯大约十年,只有人寿命的十分之一。前端项目只是你生活工作的一部分,而你却是它的全部,你是他的灵魂。请放下长时间的游戏、工作时的摸鱼。多学习来以最完美的状态好好陪你项目!

正文

主页讲解 单页面 加载 和 SEO 问题。 掘金PC还有许多功能等你开发

知识点

  • SSR & SSG & CSR
  • 预渲染 Prerender
  • 手动配置 SSR
  • Next 还原掘金PC端

CSR & SSR & SSG

CSR

客户端渲染(Client Side Rendering)

  • CSR 渲染流程

16eeb56642155f21.webp

SSR

服务端渲染(Server Side Rendering)

  • 是指将单页应用(SPA)在服务器端渲染成 HTML 片段,发送到浏览器,然后交由浏览器为其绑定状态与事件,成为完全可交互页面的过程。(PS:本文中的 SSR 内容都是围绕同构应用来讲的)
  • SSR 渲染流程:

16eeb5663f9bdfe7.webp

服务端只负责首次“渲染”(真正意义上,只有浏览器才能渲染页面,服务端其实是生成 HTML 内容),然后返回给客户端,客户端接管页面交互(事件绑定等逻辑),之后客户端路由切换时,直接通过 JS 代码来显示对应的内容,不再需要服务端渲染(只有页面刷新时会需要)

SSG

静态页面生成(Static Stie Generation)

  • 解决白屏问题、SEO问题。但无法生成用户相关内容(所以用户请求的结果都相同)。请求发生的过程中直接生成静态的HTML文件。(缺点以生成的文件难以达到自动更新,往往设置时间戳或者定时清理文件)

为什么要使用SSR

这个概念想必大家都知道

  • 解决首屏渲染速度延迟问题
  • 解决SEO搜索引擎抓取问题

不妨看下下面的图

image.png

这是CSR 的页面源码

image.png

这是SSR 的页面源码

为什么出现上述缺点,无非没有真实的HTML等导致

这就是为何要使用SSR的原因了,其实不用SSR使用预渲染也能解决SEO问题,但无法解决首屏渲染白屏的问题,从而导致用户体验的欠佳

预渲染 Prerender

  • 简单解释,用户浏览器请求和SEO的蜘蛛爬去分别转发到不同的地址

2020-03-17-12-28-52.png

以Node方式实现是这样的

const prerender = require("prerender");
const server = prerender({
  port: 5011 //更改prerender服务器端口
});
//使用prerender插件
server.use(prerender.removeScriptTags());
server.start();

其他可以参考 github.com/prerender/p…

手动配置 SSR

配置 SSR 前你需要一些 Webpack基础 可以看下我的文章, 从零学习Webpack,当然你也可以看官网

webpack.base.js

const path = require("path");

module.exports = {
  mode: "development",
  watch: true,
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src")
    },
    extensions: [".js", ".jsx", ".css"]
  },
  module: {
    rules: [
      {
        test: /\.jsx?/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-react"]
            }
          }
        ]
      }
    ]
  }
};

webpack.server.js

const path = require("path");
const baseConfig = require("./webpack.base");
const merge = require("webpack-merge");
const nodeExternals = require("webpack-node-externals");
const serverConfig = {
  devtool: "none",
  entry: "./src/server",
  target: "node",
  output: {
    filename: "server.js",
    path: path.resolve(__dirname, "./dist"),
    publicPath: "/"
  },
  externals: [nodeExternals()],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["isomorphic-style-loader", "css-loader?modules"]
      },
      {
        test: /\.(png)|(jpg)|(gif)$/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "img/[name].[hash:5].[ext]",
              emitFile: false
            }
          }
        ]
      }
    ]
  }
};
module.exports = merge(baseConfig, serverConfig);

上面配置的服务端的 webpack 客户端同理,配置好入口,避免服务端代码在客户端运行

client


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

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

官网推荐在使用服务端渲染时 调用 ReactDOM.hydrate 方法

而服务端代码责负责将DOM转成HTML字符串

express 和 koa 中间件

import React from "react";
import App from "./App";
import ReactDom from "react-dom/server";
import getHTML from "./getHTML";
export default (req, res) => {
  const context = {};
  const componentHTML = ReactDom.renderToString(
    <App location={req.path} context={context} />
  );
  const html = getHTML(componentHTML);
  res.send(html);
};

这就是手动配置 SSR 的基本过程,不够详细是为了让你们思考。

还有接下来介绍的Next js 是一款比较成熟的 React 服务端渲染框架,并不需要你手动配.

Next 还原掘金PC端

看看官网 Nextjs

  • 路由
  • SSR 和 SSG
  • Redux
  • Styles
  • Config

从上面 入手带你 学会具备掘金的开发功能

约定式路由

image.png

这个应该不用怎么介绍, 根据文件目录结构生成 路由 ,当然应该也可以手动配置

这可以直接集成 中间件反向代理

// 图中 [...args].js

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
const { createProxyMiddleware } = require('http-proxy-middleware');

export default createProxyMiddleware({
  target: 'http://localhost:3300',
  changeOrigin: true,
  pathRewrite: {
    '^/api': '',
  },
});
// 解决转发Post问题
export const config = {
  api: {
    bodyParser: false,
  },
};

SSR 和 SSG 在Next 中使用

getServerSideProps (SSR) 这个方法只在server端执行 ,有一个context上下文,有如下基本参数

  • params:如果此页面使用动态路由,则params包含路由参数。如果页面名称是[id].js,params则将看起来像{ - id: ... }。要了解更多信息,请查看动态路由文档。
  • req:HTTP IncomingMessage对象。
  • res:HTTP响应对象。
  • query:代表查询字符串的对象。
  • preview:preview表示true页面是否处于预览模式,false否则。请参阅预览模式文档。
  • previewData:设置的预览数据setPreviewData。请参阅预览模式文档。
  • resolvedUrl:请求网址的规范化版本,该版本会剥离_next/data客户端转换的前缀,并包含原始查询值。
/ 每次请求到达后都会运行
// 仅在服务器端运行
// req, res, query
export async function getServerSideProps({ query }: any) {
  const res = await Request.fetchAritcles();
  return {
    props: {
      articles: res.list,
      total: res.list.length,
    },
  };
}

一般发送接口请求服务,可以看下上面获取文章详情的部分代码,学会可以实现掘金列表

image.png

getStaticProps (SSG)

该方法参数基本 和 getServerSideProps 一样

和SSG概念一样直接生成 单独的 html 文件

export async function getStaticProps(context) {
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  if (!data) {
    return {
      notFound: true,
    }
  }

  return {
    props: { data }, // will be passed to the page component as props
  }
}

Redux

集成 Redux 和基本的 React 项目相同,唯一的问题是需要判断 是否浏览器防止仓库重新构建和方法不存在问题

比如这样

import { createStore, applyMiddleware } from "redux";
import reducer from "./reducers";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import isBrowser from "../util/isBrowser";

let store;

/**
 * 创建仓库的函数
 * 该函数保证,如果是服务器端,每一次调用产生一个新的仓库
 * 如果是客户端,每一次调用返回同一个仓库
 * @param {*} initialState 仓库的初始值
 */
export default function(initialState) {
  if (isBrowser()) {
    //客户端
    if (!store) {
      store = create(initialState);
    }
    return store; //返回已有仓库
  }
  return create(initialState);
}

function create(initialState) {
  return createStore(
    reducer,
    initialState,
    composeWithDevTools(applyMiddleware(thunk))
  );
}

只构造一个仓库

掌握这项基本技能能实现基本的登录状态管理

image.png

Styles

  • 构建够有一个 全局的 globe.css
  • 样式使用的是css module
  • 集成scss 继续往下翻一翻 看配置

掌握这些还不够,还有很多坑等着你

服务端执行顺序

  • _app getServerSideProps()
  • page getServerSideProps()
  • _document getServerSideProps()
  • _app constructor()
  • _app render()
  • page constructor()
  • page render()
  • _document constructor()
  • _document render()

客户端执行顺序(首次打开页面)

  • _app constructor()
  • _app render()
  • page constructor()
  • page render()

路由跳转执行顺序

  • _app getServerSideProps()
  • page getServerSideProps()
  • _app render()
  • page constructor()
  • page render()

_app.js

import { Provider } from 'react-redux';
import Layout from '@/layout/default';

import '../styles/globals.scss';
import 'antd/dist/antd.min.css';

import store from '../store/index';

function MyApp({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </Provider>
  );
}

export default MyApp;

提供了页面的propscomponent 便于构建模板等

假如你有些地方不需要 SSR 你得这样

// User 不使用 ssr
const User = dynamic(import('./user'), {
  ssr: false,
});

Config

我使用不多介绍一下,更多可以看官网

const path = require('path');
// 接入 scss
module.exports = {
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')],
  },
};

// 接入 typescript
module.exports = {
  typescript: {
    // !! WARN !!
    // Dangerously allow production builds to successfully complete even if
    // your project has type errors.
    // !! WARN !!
    ignoreBuildErrors: true,
  },
};

// 配置webpack
module.exports = {
  pageExtensions: ['jsx', 'js', 'ts', 'tsx'],
};
module.exports = {
  webpack: (config, { isServer }) => {
    config.resolve.alias['@'] = path.resolve(__dirname, './src');
    return config;
  },
};

这些大概可以满足开发

掘金Pc 功能靠你们了 冲!

总结

  • Next 学习成本低,简单易用,无需自己配SSR,Github使用多

  • Next 缺点显而易见,规范多,按照规则,还得封装许多方法(小问题

  • 了解SSR 、 SSG 、 CSR 、预渲染

  • 掌握 Next 框架 学会开发 掘金PC 技术