前言

当下有很多免费的静态网站托管服务,如Github PagesNetlify等,它们能很好地完成静态网站(包括使用webpack打包后发布的SPA应用)的托管。即使不使用它们,换成AWS S3之类的静态托管,也是比较廉价的。这些免费或比较便宜的静态网站托管服务,为很多前端开发提供了自由创作的平台,可以用来实现各种创意网站,而不用考虑费用的问题。

然而在需要用到SSR的时候,静态网站就满足不了需求了。如果选用VPS做SSR的话,成本一下就会增长上来许多。

那对于需要使用SSR的场景,有没有办法继续薅羊毛呢?答案是有的,Serverless的免费额度在向你招手。

Faas厂商选择

因为成本一般相差不大,所以在选择Faas厂商的时候,主要考虑的是性能和稳定性,以及开发便利性。

因为网络节点的问题,一般来说国内的云服务厂商要比国外的延迟低性能好。稳定性上我没做过深入对比,一般选用主流平台即可。

从开发的便利性来说,AWS应该是最好的,作为全球第一的云服务商,多种框架和库都会重点支持它。

本文中使用的Faas厂商是AWS,文章中使用的相关框架不一定适配所有的云服务商,请谨慎参考。

技术选型

此处以比较流行的React + Next.js 实现 SSR。同时结合 Serverless 框架进行部署。

实现方案

第一步是创建一个Next.js的应用

新建Next.js工程

这个方面可以跟着Next.js的官方教程走一遍

首先用如下的命令创建一个简单的node工程 next-serverless

  1. mkdir next-serverless
  2. cd next-serverless
  3. npm init -y
  4. npm install --save react react-dom next
  5. mkdir pages

然后修改一下package.json中的启动命令

  1. "scripts": {
  2. "dev": "next",
  3. "build": "next build",
  4. "start": "next start"
  5. }

之后就可以使用 npm run dev 启动它查看效果了。现在还没有配置页面,所以会看到一个404页面。

然后再创建一个文件 pages/index.js,加入如下内容:

  1. const Index = () => (
  2. <div>
  3. <p>Hello Next.js</p>
  4. </div>
  5. );
  6. export default Index;

就可以看到效果了。

关于Next.js就先介绍到这里,如果不熟悉Next.js的用法,建议先跟随官方文档走一遍教程。

设置Serverless

Next.js和Serverless的结合主要有两种方式:传统的Serverless Nextjs Plugin 和基于Serverless ComponentsServerless Nextjs Component。本文中使用后者,因其使用起来更简单,也是其作者推荐用法。

首先安装需要的npm依赖

  1. npm install serverless-next.js --save-dev

然后新建文件 serverless.yml,加入如下内容:

  1. # serverless.yml
  2. nextServerless:
  3. component: serverless-next.js

新建或修改 next.config.js,修改其 build target 设置

  1. // next.config.js
  2. module.exports = {
  3. target: "serverless"
  4. };

设置对应云服务厂商的Serverless凭证,参考Serverless教程

serverless-next.js目前似乎只支持AWS,

设置好Serverless凭证之后,就可以开始部署了。部署的命令非常简单:

先全局安装serverless

  1. npm i -g serverless

再执行serverless

  1. serverless

即可。

自定义域名

Serverless部署之后,AWS会提供一个节点地址,但是这个地址太长不好记,所以一般我们都会需要用到自定义域名。

设置自定义域名主要是两种方法,一是手动添加DNS解析,设置CName。二是如果你的域名被AWS的Route53管理着,则可以直接在 serverless.yml 中配置域名:

  1. # serverless.yml
  2. nextServerless:
  3. component: serverless-next.js
  4. inputs:
  5. domain: ["sub", "example.com"] # [ sub-domain, domain ]

Serverless.yml中还可以指定更多的选项,具体请参考Serverless Next.js的Readme文档。

Typescript支持

添加如下的 tsconfig.json

  1. {
  2. "compilerOptions": {
  3. "target": "esnext",
  4. "lib": [
  5. "dom",
  6. "dom.iterable",
  7. "esnext"
  8. ],
  9. "allowJs": true,
  10. "skipLibCheck": true,
  11. "esModuleInterop": true,
  12. "allowSyntheticDefaultImports": true,
  13. "strict": true,
  14. "forceConsistentCasingInFileNames": true,
  15. "module": "esnext",
  16. "moduleResolution": "node",
  17. "resolveJsonModule": true,
  18. "isolatedModules": true,
  19. "noEmit": true,
  20. "jsx": "preserve"
  21. }
  22. }

如果使用了一些第三方库(如next-ga)没有类型声明,还可以再添加一个 global.d.ts

  1. declare module 'next-ga'

按需加载组件库

在使用一些组件库,如Ant Design、Material-UI时,往往不需要用到其所有的组件。如果不做处理,默认打包了所有的组件,会有较大的浪费。

以Ant Design举例,可以使用 babel-plugin-import 进行按需加载。

首先创建 .babelrc 文件

  1. {
  2. "presets": ["next/babel"],
  3. "plugins": [
  4. ["import", { "libraryName": "antd", "style": "css" }]
  5. ]
  6. }

然后再修改next.config.js文件

  1. // next.config.js
  2. const withLess = require('@zeit/next-less')
  3. const withCSS = require('@zeit/next-css')
  4. module.exports = withCSS(withLess({
  5. /* config options here */
  6. target: 'serverless',
  7. webpack: (config, { isServer }) => {
  8. if (isServer) {
  9. const antStyles = /antd\/.*?\/style\/css.*?/
  10. const origExternals = [...config.externals]
  11. config.externals = [
  12. (context, request, callback) => {
  13. if (request.match(antStyles)) return callback()
  14. if (typeof origExternals[0] === 'function') {
  15. origExternals[0](context, request, callback)
  16. } else {
  17. callback()
  18. }
  19. },
  20. ...(typeof origExternals[0] === 'function' ? [] : origExternals)
  21. ]
  22. config.module.rules.unshift({
  23. test: antStyles,
  24. use: 'null-loader'
  25. })
  26. }
  27. return config
  28. }
  29. }))

也可以参考下next.js提供的demo工程

设置页面头部信息

SSR应用一般需要设置一些信息在 <head></head> 块之中,如 <meta />块、<title />块等。

在Next.js中,可以使用next/head来实现。

示例代码:

  1. import Head from 'next/head'
  2. function IndexPage() {
  3. return (
  4. <div>
  5. <Head>
  6. <title>My page title</title>
  7. <meta name="viewport" content="initial-scale=1.0, width=device-width" />
  8. </Head>
  9. <p>Hello world!</p>
  10. </div>
  11. )
  12. }
  13. export default IndexPage

服务端渲染vs客户端渲染

当页面中需要请求外部API时,视具体情况,可选择在服务端渲染或在客户端渲染。

如果在客户端渲染,可以使用 componentDidMountuseEffect等,而如果要选择服务端渲染,next.js提供了一个机制:getInitialProps

示例代码:

  1. import React from 'react'
  2. import fetch from 'isomorphic-unfetch'
  3. class Page extends React.Component {
  4. static async getInitialProps(ctx) {
  5. const res = await fetch('https://api.github.com/repos/zeit/next.js')
  6. const json = await res.json()
  7. return { stars: json.stargazers_count }
  8. }
  9. render() {
  10. return <div>Next stars: {this.props.stars}</div>
  11. }
  12. }
  13. export default Page

性能

Serverless Nextjs Component 使用了 AWS 的Lambda Edge技术,会就近使用节点向用户提供内容。在国内访问的话,一般会从日本节点提供服务。

以我做过的简单应用举例,首屏加载平均需要1~2s的时间。会比国内的节点慢一些。


以上就是使用Serverless构建SSR前端应用的简单实现,希望对你有所帮助。