前言
当下有很多免费的静态网站托管服务,如Github Pages和Netlify等,它们能很好地完成静态网站(包括使用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
mkdir next-serverless
cd next-serverless
npm init -y
npm install --save react react-dom next
mkdir pages
然后修改一下package.json
中的启动命令
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
之后就可以使用 npm run dev
启动它查看效果了。现在还没有配置页面,所以会看到一个404页面。
然后再创建一个文件 pages/index.js
,加入如下内容:
const Index = () => (
<div>
<p>Hello Next.js</p>
</div>
);
export default Index;
就可以看到效果了。
关于Next.js就先介绍到这里,如果不熟悉Next.js的用法,建议先跟随官方文档走一遍教程。
设置Serverless
Next.js和Serverless的结合主要有两种方式:传统的Serverless Nextjs Plugin 和基于Serverless Components的Serverless Nextjs Component。本文中使用后者,因其使用起来更简单,也是其作者推荐用法。
首先安装需要的npm依赖
npm install serverless-next.js --save-dev
然后新建文件 serverless.yml
,加入如下内容:
# serverless.yml
nextServerless:
component: serverless-next.js
新建或修改 next.config.js
,修改其 build target 设置
// next.config.js
module.exports = {
target: "serverless"
};
设置对应云服务厂商的Serverless凭证,参考Serverless教程
serverless-next.js目前似乎只支持AWS,
设置好Serverless凭证之后,就可以开始部署了。部署的命令非常简单:
先全局安装serverless
npm i -g serverless
再执行serverless
serverless
即可。
自定义域名
Serverless部署之后,AWS会提供一个节点地址,但是这个地址太长不好记,所以一般我们都会需要用到自定义域名。
设置自定义域名主要是两种方法,一是手动添加DNS解析,设置CName。二是如果你的域名被AWS的Route53管理着,则可以直接在 serverless.yml 中配置域名:
# serverless.yml
nextServerless:
component: serverless-next.js
inputs:
domain: ["sub", "example.com"] # [ sub-domain, domain ]
Serverless.yml中还可以指定更多的选项,具体请参考Serverless Next.js的Readme文档。
Typescript支持
添加如下的 tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
}
}
如果使用了一些第三方库(如next-ga)没有类型声明,还可以再添加一个 global.d.ts
declare module 'next-ga'
按需加载组件库
在使用一些组件库,如Ant Design、Material-UI时,往往不需要用到其所有的组件。如果不做处理,默认打包了所有的组件,会有较大的浪费。
以Ant Design举例,可以使用 babel-plugin-import 进行按需加载。
首先创建 .babelrc 文件
{
"presets": ["next/babel"],
"plugins": [
["import", { "libraryName": "antd", "style": "css" }]
]
}
然后再修改next.config.js文件
// next.config.js
const withLess = require('@zeit/next-less')
const withCSS = require('@zeit/next-css')
module.exports = withCSS(withLess({
/* config options here */
target: 'serverless',
webpack: (config, { isServer }) => {
if (isServer) {
const antStyles = /antd\/.*?\/style\/css.*?/
const origExternals = [...config.externals]
config.externals = [
(context, request, callback) => {
if (request.match(antStyles)) return callback()
if (typeof origExternals[0] === 'function') {
origExternals[0](context, request, callback)
} else {
callback()
}
},
...(typeof origExternals[0] === 'function' ? [] : origExternals)
]
config.module.rules.unshift({
test: antStyles,
use: 'null-loader'
})
}
return config
}
}))
也可以参考下next.js提供的demo工程。
设置页面头部信息
SSR应用一般需要设置一些信息在 <head></head>
块之中,如 <meta />
块、<title />
块等。
在Next.js中,可以使用next/head来实现。
示例代码:
import Head from 'next/head'
function IndexPage() {
return (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
)
}
export default IndexPage
服务端渲染vs客户端渲染
当页面中需要请求外部API时,视具体情况,可选择在服务端渲染或在客户端渲染。
如果在客户端渲染,可以使用 componentDidMount
、useEffect
等,而如果要选择服务端渲染,next.js提供了一个机制:getInitialProps
示例代码:
import React from 'react'
import fetch from 'isomorphic-unfetch'
class Page extends React.Component {
static async getInitialProps(ctx) {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
render() {
return <div>Next stars: {this.props.stars}</div>
}
}
export default Page
性能
Serverless Nextjs Component 使用了 AWS 的Lambda Edge技术,会就近使用节点向用户提供内容。在国内访问的话,一般会从日本节点提供服务。
以我做过的简单应用举例,首屏加载平均需要1~2s的时间。会比国内的节点慢一些。
以上就是使用Serverless构建SSR前端应用的简单实现,希望对你有所帮助。