{"componentChunkName":"component---src-templates-best-practice-detail-tsx","path":"/best-practice/2020-06-10-ssr-yuga","result":{"data":{"currentBlog":{"id":"235ebbe5-7f0a-524b-99a9-06860ace33fb","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020610/1591775415513-serverless%2Bssr.png","authors":["yugasun"],"categories":["best-practice"],"date":"2020-06-10T00:00:00.000Z","title":"前端福音：Serverless 和 SSR 的天作之合","description":"Serverless 给前端带来了什么，前端为什么要关注 Serverless？","authorslink":["https://github.com/yugasun"],"translators":null,"translatorslink":null,"tags":["Serverless SSR","前端"],"keywords":"Serverless SSR,Serverless Egg.js","outdated":null},"wordCount":{"words":456,"sentences":88,"paragraphs":87},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-06-10-ssr-yuga.md","fields":{"slug":"/best-practice/2020-06-10-ssr-yuga/","keywords":["Javascript","nextjs","serverless","服务端渲染","客户端渲染","云函数","Serverless","serverless","部署","center"]},"html":"<h2 id=\"什么是-ssr\"><a href=\"#%E4%BB%80%E4%B9%88%E6%98%AF-ssr\" aria-label=\"什么是 ssr permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>什么是 SSR</h2>\n<p>SSR 顾名思义就是 <code class=\"language-text\">Server-Side Render</code>, 即服务端渲染。原理很简单，就是服务端直接渲染出 HTML 字符串模板，浏览器可以直接解析该字符串模版显示页面，因此首屏的内容不再依赖 Javascript 的渲染（CSR - 客户端渲染）。</p>\n<!--more-->\n<p>SSR 的核心优势：</p>\n<ol>\n<li>首屏加载时间：因为是 HTML 直出，浏览器可以直接解析该字符串模版显示页面。</li>\n<li>SEO 友好：正是因为服务端渲染输出到浏览器的是完备的 html 字符串，使得搜索引擎\n能抓取到真实的内容，利于 SEO。</li>\n</ol>\n<p>SSR 需要注意的问题：</p>\n<ol>\n<li>虽然 SSR 能快速呈现页面，但是在 UI 框架（比如 React）加载成功之前，页面是没法进行 UI 交互的。</li>\n<li>TTFB (Time To First Byte)，即第一字节时间会变长，因为 SSR 相对于 CSR 需要在服务端渲染出更对的 HTML 片段，因此加载时间会变长。</li>\n<li>更多的服务器端负载。由于 SSR 需要依赖 Node.js 服务渲染页面，显然会比仅仅提供静态文件的 CSR 应用需要占用更多服务器 CPU 资源。以 React 为例，它的 <code class=\"language-text\">renderToString()</code> 方法是同步 CPU 绑定调用，这就意味着在它完成之前，服务器是无法处理其他请求的。因此在高并发场景，需要准备相应的服务器负载，并且做好缓存策略。</li>\n</ol>\n<h2 id=\"什么是-serverless\"><a href=\"#%E4%BB%80%E4%B9%88%E6%98%AF-serverless\" aria-label=\"什么是 serverless permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>什么是 Serverless</h2>\n<p>Serverless，它是云计算发展过程中出现的一种计算资源的抽象，依赖第三方服务，开发者可以更加专注的开发自己的业务代码，而无需关心底层资源的分配、扩容和部署。</p>\n<p><strong>特点：</strong></p>\n<ol>\n<li>开发者只需要专注于业务，无需关心底层资源的分配、扩容和部署</li>\n<li>按需使用和收费</li>\n<li>自动扩缩容</li>\n</ol>\n<p>更详细的有关 Serverless 介绍，推荐阅读：<a href=\"https://zhuanlan.zhihu.com/p/58877583\">精读《Serverless 给前端带来了什么》</a></p>\n<h2 id=\"serverless--ssr\"><a href=\"#serverless--ssr\" aria-label=\"serverless  ssr permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Serverless + SSR</h2>\n<p>结合 Serverless 和 SSR 的特点，我们可以发现他们简直是天作之合。借助 Serverless，前端团队无需关注 SSR 服务器的部署、运维和扩容，可以极大地减少部署运维成本，更好的聚焦业务开发，提高开发效率。</p>\n<p>同时也无需关心 SSR 服务器的性能问题，理论上 Serverless 是可以无限扩容的（当然云厂商对于一般用户是有扩容上限的）。</p>\n<h2 id=\"如何快速将-ssr-应用-serverless-化？\"><a href=\"#%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%B0%86-ssr-%E5%BA%94%E7%94%A8-serverless-%E5%8C%96%EF%BC%9F\" aria-label=\"如何快速将 ssr 应用 serverless 化？ permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>如何快速将 SSR 应用 Serverless 化？</h2>\n<p>既然说 Serverless 对于 SSR 来说有天然的优势，那么我们如何将 SSR 应用迁移到Serverless 架构上呢？</p>\n<p>本文将以 Next.js 框架为例，带大家快速体验部署一个 Serverless SSR 应用。</p>\n<p>借助 Serverless Framework 的 <a href=\"https://github.com/serverless-components/tencent-nextjs/tree/v2\">Nextjs 组件</a>，基本可以实现无缝迁移到腾讯云云函数 SCF 上。</p>\n<h3 id=\"1-初始化-nextjs-项目\"><a href=\"#1-%E5%88%9D%E5%A7%8B%E5%8C%96-nextjs-%E9%A1%B9%E7%9B%AE\" aria-label=\"1 初始化 nextjs 项目 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>1. 初始化 Next.js 项目</h3>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"80640668076572560000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ npm init next-app serverless-next\n\\$ cd serverless-next\n\n# 编译静态文件\n\\$ npm run build`, `80640668076572560000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">npm</span> init next-app serverless-next\n$ <span class=\"token builtin class-name\">cd</span> serverless-next\n\n<span class=\"token comment\"># 编译静态文件</span>\n$ <span class=\"token function\">npm</span> run build</code></pre></div>\n<h3 id=\"2-全局安装-serverless-cli\"><a href=\"#2-%E5%85%A8%E5%B1%80%E5%AE%89%E8%A3%85-serverless-cli\" aria-label=\"2 全局安装 serverless cli permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>2. 全局安装 Serverless CLI</h3>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"43357748916879000000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ npm install serverless -g`, `43357748916879000000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">npm</span> <span class=\"token function\">install</span> serverless -g</code></pre></div>\n<h3 id=\"3-配置-severlessyml\"><a href=\"#3-%E9%85%8D%E7%BD%AE-severlessyml\" aria-label=\"3 配置 severlessyml permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>3. 配置 severless.yml</h3>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"14925402494424844000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`org: orgDemo\napp: appDemo\nstage: dev\ncomponent: nextjs\nname: nextjsDemo\n\ninputs:\n  src: ./\n  functionName: nextjsDemo\n  region: ap-guangzhou\n  runtime: Nodejs10.15\n  exclude:\n    - .env\n  apigatewayConf:\n    protocols:\n      - https\n    environment: release`, `14925402494424844000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">org</span><span class=\"token punctuation\">:</span> orgDemo\n<span class=\"token key atrule\">app</span><span class=\"token punctuation\">:</span> appDemo\n<span class=\"token key atrule\">stage</span><span class=\"token punctuation\">:</span> dev\n<span class=\"token key atrule\">component</span><span class=\"token punctuation\">:</span> nextjs\n<span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> nextjsDemo\n\n<span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">src</span><span class=\"token punctuation\">:</span> ./\n  <span class=\"token key atrule\">functionName</span><span class=\"token punctuation\">:</span> nextjsDemo\n  <span class=\"token key atrule\">region</span><span class=\"token punctuation\">:</span> ap<span class=\"token punctuation\">-</span>guangzhou\n  <span class=\"token key atrule\">runtime</span><span class=\"token punctuation\">:</span> Nodejs10.15\n  <span class=\"token key atrule\">exclude</span><span class=\"token punctuation\">:</span>\n    <span class=\"token punctuation\">-</span> .env\n  <span class=\"token key atrule\">apigatewayConf</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">protocols</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> https\n    <span class=\"token key atrule\">environment</span><span class=\"token punctuation\">:</span> release</code></pre></div>\n<h3 id=\"4-部署\"><a href=\"#4-%E9%83%A8%E7%BD%B2\" aria-label=\"4 部署 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>4. 部署</h3>\n<p>部署时需要进行身份验证，如您的账号未 <code class=\"language-text\">登录</code> 或 <code class=\"language-text\">注册</code> 腾讯云，您可以直接通过 <code class=\"language-text\">微信</code> 扫描命令行中的二维码进行授权登陆和注册。当然你也可以直接在项目下面创建 <code class=\"language-text\">.env</code> 文件，配置腾讯云的 <code class=\"language-text\">SecretId</code> 和 <code class=\"language-text\">SecretKey</code>。如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"45356770920217485000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`TENCENT_SECRET_ID=123\nTENCENT_SECRET_KEY=123`, `45356770920217485000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"dotenv\"><pre class=\"language-dotenv\"><code class=\"language-dotenv\">TENCENT_SECRET_ID=123\nTENCENT_SECRET_KEY=123</code></pre></div>\n<p>执行部署命令：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"70812993903803020000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ serverless deploy`, `70812993903803020000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ serverless deploy</code></pre></div>\n<blockquote>\n<p>以下 <code class=\"language-text\">serverless</code> 命令全部简写为 <code class=\"language-text\">sls</code>.</p>\n</blockquote>\n<p>部署成功后，直接访问 API 网关生成的域名，这里是就可以了。</p>\n<blockquote>\n<p>类似 <code class=\"language-text\">https://service-xxx-xxx.gz.apigw.tencentcs.com/release/</code> 这种链接。</p>\n</blockquote>\n<h3 id=\"现有-nextjs-应用迁移\"><a href=\"#%E7%8E%B0%E6%9C%89-nextjs-%E5%BA%94%E7%94%A8%E8%BF%81%E7%A7%BB\" aria-label=\"现有 nextjs 应用迁移 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>现有 Next.js 应用迁移</h3>\n<p>如果你的项目是基于 Express.js 的自定义 Server，那么需要在项目根目录新建 sls.js 入口文件，只需要将原来启动 Node.js Server 的入口文件复制到 sls.js 中，然后进行少量改造就好，默认入口 <code class=\"language-text\">sls.js</code> 文件如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"88629086416851940000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const express = require('express');\nconst next = require('next');\nconst app = next({ dev: false });\nconst handle = app.getRequestHandler();\n\n// 将原来的服务逻辑放入到异步函数 \\`createServer()\\`中\nasync function createServer() {\n  // 内部内容需要根据项目需求进行修改就好，基本是你的 \\`server.js\\` 的原代码\n  await app.prepare();\n  const server = express();\n\n  server.all('*', (req, res) => {\n    return handle(req, res);\n  });\n\n  // 定义返回二进制文件类型\n  // 由于 Next.js 框架默认开启 \\`gzip\\`，所以这里需要配合为 \\`['*/*']\\`\n  // 如果项目关闭了 \\`gzip\\` 压缩，那么对于图片类文件，需要定制化配置，比如 \\`['image/jpeg', 'image/png']\\`\n  server.binaryTypes = ['*/*'];\n\n  return server;\n}\n\n// export 函数 createServer()\nmodule.exports = createServer;`, `88629086416851940000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> express <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'express'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> next <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'next'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> app <span class=\"token operator\">=</span> <span class=\"token function\">next</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> dev<span class=\"token punctuation\">:</span> <span class=\"token boolean\">false</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> handle <span class=\"token operator\">=</span> app<span class=\"token punctuation\">.</span><span class=\"token function\">getRequestHandler</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 将原来的服务逻辑放入到异步函数 `createServer()`中</span>\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">createServer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// 内部内容需要根据项目需求进行修改就好，基本是你的 `server.js` 的原代码</span>\n  <span class=\"token keyword\">await</span> app<span class=\"token punctuation\">.</span><span class=\"token function\">prepare</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> <span class=\"token function\">express</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  server<span class=\"token punctuation\">.</span><span class=\"token function\">all</span><span class=\"token punctuation\">(</span><span class=\"token string\">'*'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> res</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">handle</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// 定义返回二进制文件类型</span>\n  <span class=\"token comment\">// 由于 Next.js 框架默认开启 `gzip`，所以这里需要配合为 `['*/*']`</span>\n  <span class=\"token comment\">// 如果项目关闭了 `gzip` 压缩，那么对于图片类文件，需要定制化配置，比如 `['image/jpeg', 'image/png']`</span>\n  server<span class=\"token punctuation\">.</span>binaryTypes <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">'*/*'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> server<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// export 函数 createServer()</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> createServer<span class=\"token punctuation\">;</span></code></pre></div>\n<p>添加入口文件后，重新执行部署命令 <code class=\"language-text\">sls deploy</code> 就 OK 了。</p>\n<h2 id=\"serverless-部署方案的优化\"><a href=\"#serverless-%E9%83%A8%E7%BD%B2%E6%96%B9%E6%A1%88%E7%9A%84%E4%BC%98%E5%8C%96\" aria-label=\"serverless 部署方案的优化 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Serverless 部署方案的优化</h2>\n<p>至此，我们已经成功将整个 Next.js 应用迁移到腾讯云的 Serverless 架构上了，但是这里有个问题，就是所有的静态资源都部署到了云函数 SCF 中，这就导致我们每次页面请求的同时，会产生很多静态源请求，对于 SCF 来说同一时间并发会比较高，而且很容易造成冷启动。而且大量静态资源通过 SCF 输出，然后经过 API 网关返回，会额外增加链路长度，也会导致静态资源加载慢，无形中也会拖累网页的加载速度。</p>\n<p>云厂商一般会提供云对象存储功能，腾讯云叫 COS（对象存储），用它来存储我们的静态资源有天然的优势。而且开始使用有 <code class=\"language-text\">50GB!!!</code> 的免费容量（用来存喜爱的高清电影也是不错的吧~）。</p>\n<p>要是在我们项目部署时，将静态资源统一上传到 COS，然后静态页面通过 SCF 渲染，这样既支持了 SSR，也解决了静态资源访问问题。而且 COS 也支持 CDN 加速，这样静态资源优化就更加方便。</p>\n<p>那么我们如何将静态资源上传到 COS 呢？</p>\n<h3 id=\"普通青年做法\"><a href=\"#%E6%99%AE%E9%80%9A%E9%9D%92%E5%B9%B4%E5%81%9A%E6%B3%95\" aria-label=\"普通青年做法 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>普通青年做法</h3>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">登录 [腾讯云 COS 控制台](https://console.cloud.tencent.com/cos5) -&gt; 创建存储桶 -&gt; 获取 COS 访问链接 -&gt; 构建 Next.js 项目 -&gt; 点击 COS 上传按钮 -&gt; 选择上传文件 -&gt; 开始上传 -&gt; 完成</code></pre></div>\n<h3 id=\"文艺青年做法\"><a href=\"#%E6%96%87%E8%89%BA%E9%9D%92%E5%B9%B4%E5%81%9A%E6%B3%95\" aria-label=\"文艺青年做法 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>文艺青年做法</h3>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">配置 COS 组件 -&gt; 构建 Next.js 项目 -&gt; 执行部署 COS 组件命令 -&gt; 完成</code></pre></div>\n<p>接下来我们一起学习下文艺青年是如何做的。</p>\n<p>在项目下创建 COS 文件夹，创建 <code class=\"language-text\">cos/serverless.yml</code> 配置文件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"82347674428325840000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`org: orgDemo\napp: appDemo\nstage: dev\ncomponent: cos\nname: serverless-cos\n\ninputs:\n  # src 配置成你的next项目构建的目标目录\n  src: ../.next/static\n  # 由于 next框架在访问静态文件会自动附加 _next 前缀，所以这里需要配置上传 COS 的目标目录为 /_next\n  targetDir: /_next/static\n  bucket: serverless-bucket\n  region: ap-guangzhou\n  protocol: https\n  acl:\n    permissions: public-read`, `82347674428325840000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">org</span><span class=\"token punctuation\">:</span> orgDemo\n<span class=\"token key atrule\">app</span><span class=\"token punctuation\">:</span> appDemo\n<span class=\"token key atrule\">stage</span><span class=\"token punctuation\">:</span> dev\n<span class=\"token key atrule\">component</span><span class=\"token punctuation\">:</span> cos\n<span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> serverless<span class=\"token punctuation\">-</span>cos\n\n<span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token comment\"># src 配置成你的next项目构建的目标目录</span>\n  <span class=\"token key atrule\">src</span><span class=\"token punctuation\">:</span> ../.next/static\n  <span class=\"token comment\"># 由于 next框架在访问静态文件会自动附加 _next 前缀，所以这里需要配置上传 COS 的目标目录为 /_next</span>\n  <span class=\"token key atrule\">targetDir</span><span class=\"token punctuation\">:</span> /_next/static\n  <span class=\"token key atrule\">bucket</span><span class=\"token punctuation\">:</span> serverless<span class=\"token punctuation\">-</span>bucket\n  <span class=\"token key atrule\">region</span><span class=\"token punctuation\">:</span> ap<span class=\"token punctuation\">-</span>guangzhou\n  <span class=\"token key atrule\">protocol</span><span class=\"token punctuation\">:</span> https\n  <span class=\"token key atrule\">acl</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">permissions</span><span class=\"token punctuation\">:</span> public<span class=\"token punctuation\">-</span>read</code></pre></div>\n<p>根据 COS 访问链接生成规则：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"96132325942029390000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`<protocol>://<bucket-name>-<appid>.cos.<region>.myqcloud.com`, `96132325942029390000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token operator\">&lt;</span>protocol<span class=\"token operator\">></span>://<span class=\"token operator\">&lt;</span>bucket-name<span class=\"token operator\">></span>-<span class=\"token operator\">&lt;</span>appid<span class=\"token operator\">></span>.cos.<span class=\"token operator\">&lt;</span>region<span class=\"token operator\">></span>.myqcloud.com</code></pre></div>\n<p>可以直接推断出部署后的访问 URL 为：<code class=\"language-text\">https://serverless-bucket-1251556596.cos.ap-guangzhou.myqcloud.com</code></p>\n<p>然后在项目更目录新建 <code class=\"language-text\">next.config.js</code> 文件，配置 <code class=\"language-text\">assetPrefix</code> 为该链接：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"55201985609239945000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const isProd = process.env.NODE_ENV === 'production';\nmodule.exports = {\n  assetPrefix: isProd\n    ? 'https://serverless-bucket-1251556596.cos.ap-guangzhou.myqcloud.com'\n    : '',\n};`, `55201985609239945000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> isProd <span class=\"token operator\">=</span> process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span><span class=\"token constant\">NODE_ENV</span> <span class=\"token operator\">===</span> <span class=\"token string\">'production'</span><span class=\"token punctuation\">;</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  assetPrefix<span class=\"token punctuation\">:</span> isProd\n    <span class=\"token operator\">?</span> <span class=\"token string\">'https://serverless-bucket-1251556596.cos.ap-guangzhou.myqcloud.com'</span>\n    <span class=\"token punctuation\">:</span> <span class=\"token string\">''</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<blockquote>\n<p>注意：如果你是直接给该 COS 配置了 CDN 域名。</p>\n</blockquote>\n<p>然后执行构建：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"48568487176962050000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ npm run build`, `48568487176962050000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">npm</span> run build</code></pre></div>\n<p>然后部署命令新增部署到 cos 命令执行就好：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"68313577917977720000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ sls deploy --target=./cos && sls deploy`, `68313577917977720000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ sls deploy --target<span class=\"token operator\">=</span>./cos <span class=\"token operator\">&amp;&amp;</span> sls deploy</code></pre></div>\n<p>然后我们就可以耐心等待部署完成。</p>\n<h2 id=\"serverless--nextjs-部署流程图\"><a href=\"#serverless--nextjs-%E9%83%A8%E7%BD%B2%E6%B5%81%E7%A8%8B%E5%9B%BE\" aria-label=\"serverless  nextjs 部署流程图 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Serverless + Next.js 部署流程图</h2>\n<p>优化后项目整体部署流程图如下：</p>\n<center>\n<img src=\"https://blog-1251556596.file.myqcloud.com/deploy-flow.png\" width=\"800\" alt=\"Depoy flow\">\n</center>\n<p>起初虽然看起来步骤很多，但是项目配置一次后，之后部署，只需要执行构建和部署命令，就可以了。</p>\n<h2 id=\"性能分析\"><a href=\"#%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90\" aria-label=\"性能分析 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>性能分析</h2>\n<p>依赖 Serverless Component, 虽然我们可以快速部署 SSR 应用。但是对于开发者来说，性能才是最重要的。那么 Serverless 方案的性能表现如何呢？</p>\n<p>为了跟传统的 SSR 服务做对比，我专门找了一台 CVM （腾讯云服务器），然后部署相同的 Next.js 应用。分别进行压测和性能分析。</p>\n<p>压测配置如下：</p>\n<table>\n<thead>\n<tr>\n<th>起始人数</th>\n<th>每阶段增加人数</th>\n<th>每阶段持续时间(s)</th>\n<th>最大人数</th>\n<th>发包间隔时间(ms)</th>\n<th>超时时间(ms)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>5</td>\n<td>5</td>\n<td>30</td>\n<td>100</td>\n<td>0</td>\n<td>10000</td>\n</tr>\n</tbody>\n</table>\n<blockquote>\n<p>本文压测使用的是 <a href=\"https://wetest.qq.com/\">腾讯 WeTest</a>。</p>\n</blockquote>\n<h2 id=\"页面访问性能对比\"><a href=\"#%E9%A1%B5%E9%9D%A2%E8%AE%BF%E9%97%AE%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94\" aria-label=\"页面访问性能对比 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>页面访问性能对比</h2>\n<p>均使用 Chrome 浏览器</p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">方案</th>\n<th align=\"center\">配置</th>\n<th align=\"center\">TTFB</th>\n<th align=\"center\">FCP</th>\n<th align=\"center\">TTI</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\">腾讯云 CVM</td>\n<td align=\"center\">2 核，4G 内存，10M 带宽</td>\n<td align=\"center\">50.12ms</td>\n<td align=\"center\">2.0s</td>\n<td align=\"center\">2.1s</td>\n</tr>\n<tr>\n<td align=\"left\">腾讯云 Serverless</td>\n<td align=\"center\">128M 内存</td>\n<td align=\"center\">69.88ms</td>\n<td align=\"center\">2.0s</td>\n<td align=\"center\">2.2s</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"压测性能对比\"><a href=\"#%E5%8E%8B%E6%B5%8B%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94\" aria-label=\"压测性能对比 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>压测性能对比</h2>\n<h3 id=\"1响应时间：\"><a href=\"#1%E5%93%8D%E5%BA%94%E6%97%B6%E9%97%B4%EF%BC%9A\" aria-label=\"1响应时间： permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>1.响应时间：</h3>\n<center>\n<img src=\"https://blog-1251556596.file.myqcloud.com/cvm-response-time.png\" width=\"600\" alt=\"CVM Response Time\">\n</center>\n<center>\n<img src=\"https://blog-1251556596.file.myqcloud.com/serverless-response-time.png\" width=\"600\" alt=\"Serverless Response Time\">\n</center>\n<table>\n<thead>\n<tr>\n<th align=\"left\">方案</th>\n<th align=\"center\">配置</th>\n<th align=\"center\">最大响应时间</th>\n<th align=\"center\">P95 耗时</th>\n<th align=\"center\">P50 耗时</th>\n<th align=\"center\">平均响应时间</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\">腾讯云 CVM</td>\n<td align=\"center\">2 核，4G 内存，10M 带宽</td>\n<td align=\"center\">8830ms</td>\n<td align=\"center\">298ms</td>\n<td align=\"center\">35ms</td>\n<td align=\"center\">71.05 ms</td>\n</tr>\n<tr>\n<td align=\"left\">腾讯云 Serverless</td>\n<td align=\"center\">128M 内存</td>\n<td align=\"center\">1733ms</td>\n<td align=\"center\">103ms</td>\n<td align=\"center\">73ms</td>\n<td align=\"center\">76.78 ms</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"2tps\"><a href=\"#2tps\" aria-label=\"2tps permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>2.TPS:</h3>\n<center>\n<img src=\"https://blog-1251556596.file.myqcloud.com/cvm-tps.png\" width=\"600\" alt=\"CVM TPS\">\n</center>\n<center>\n<img src=\"https://blog-1251556596.file.myqcloud.com/serverless-tps.png\" width=\"600\" alt=\"Serverless TPS\">\n</center>\n<table>\n<thead>\n<tr>\n<th align=\"left\">方案</th>\n<th align=\"center\">配置</th>\n<th>平均 TPS</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\">腾讯云 CVM</td>\n<td align=\"center\">2 核，4G 内存，10M 带宽</td>\n<td>727.09 /s</td>\n</tr>\n<tr>\n<td align=\"left\">腾讯云 Serverless</td>\n<td align=\"center\">128M 内存</td>\n<td>675.59 /s</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"价格预算对比\"><a href=\"#%E4%BB%B7%E6%A0%BC%E9%A2%84%E7%AE%97%E5%AF%B9%E6%AF%94\" aria-label=\"价格预算对比 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>价格预算对比</h2>\n<p>直接上图：</p>\n<center>\n<img src=\"https://blog-1251556596.file.myqcloud.com/budget.png\" width=\"600\" alt=\"CVM vs Serverless Budget\">\n</center>\n<h2 id=\"对比分析\"><a href=\"#%E5%AF%B9%E6%AF%94%E5%88%86%E6%9E%90\" aria-label=\"对比分析 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>对比分析</h2>\n<p>从单用户访问页面性能表现来看 <code class=\"language-text\">Serverless 方案略逊于服务器方案</code>，但是页面性能指标是可以优化的。从压测来看，虽然 Serverless 的 <code class=\"language-text\">平均响应时间</code> 略大于 CVM，但是 <code class=\"language-text\">最大响应时间</code> 和 <code class=\"language-text\">P95耗时</code> 均优于 CVM 很多，CVM 的最大响应时间甚至接近 Serverless 的 <code class=\"language-text\">3倍</code>。而且当并发量逐渐增大时，CVM 的响应时间变化明显，而且越来越大，而 Serverless 则表现平稳，除了极个别的冷启动，基本能在 <code class=\"language-text\">200ms</code> 以内。</p>\n<p>由此可以看出，随着并发的增加，SSR 会导致服务器负荷越来越大，从而会加大服务器的响应时间；而 Serverless 由于具有自动扩缩的能力，所以相对比较平稳。</p>\n<p>当然由于测试条件有限，可能会有考虑不够全面的地方，但是从压测图形来看，是完全符合理论预期的。</p>\n<p>但是从价格对比来看，接近配置的 Serverless 方案基本不怎么花钱，甚至很多时候，免费额度就已经可以满足需求了，这里为了增加 Serverless 费用，估计调大了调用次数，内存大小，但是即便如此，服务器方案还是接近 Serverless 方案的 10 倍!!!!!。</p>\n<h2 id=\"最后\"><a href=\"#%E6%9C%80%E5%90%8E\" aria-label=\"最后 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>最后</h2>\n<p>写到这，作为一名前端开发，我的内心是无比激动的。记得以前在项目中为了优化首屏时间和 SEO，就做个好几个方案的对比，但是最终因为公司运维团队的不够配合，最后放弃了 SSR，最后选择了前端可掌控的 <code class=\"language-text\">预渲染方案</code>。现在有了 Serverless，前端终于不用受运维的限制，可以基于 Serverless 来大胆的尝试 SSR。而且借助 Serverless，前端还可以做的更多。</p>\n<p>当然真正的 SSR 并不止如此，要达到页面极致体验我们还需要做很多工作，比如：</p>\n<ol>\n<li>静态资源部署到 CDN</li>\n<li>页面缓存</li>\n<li>降级处理</li>\n<li>...</li>\n</ol>\n<p>但是这些无论是部署到服务器还是 Serverless，都是我们需要做的工作。并不会成为我们将 SSR 部署到 Serverless 的绊脚石。</p>\n<p>如果你对 Serverless Component 开发感兴趣，欢迎一起学习讨论。</p>\n<hr>\n<div id='scf-deploy-iframe-or-md'></div>\n<hr>\n<blockquote>\n<p><strong>传送门：</strong></p>\n<ul>\n<li>GitHub: <a href=\"https://github.com/serverless/serverless/blob/master/README_CN.md\">github.com/serverless</a></li>\n<li>官网：<a href=\"https://serverless.com/\">serverless.com</a></li>\n</ul>\n</blockquote>\n<p>欢迎访问：<a href=\"https://serverlesscloud.cn/\">Serverless 中文网</a>，您可以在 <a href=\"https://serverlesscloud.cn/best-practice\">最佳实践</a> 里体验更多关于 Serverless 应用的开发！</p>","tableOfContents":"<ul>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E4%BB%80%E4%B9%88%E6%98%AF-ssr\">什么是 SSR</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E4%BB%80%E4%B9%88%E6%98%AF-serverless\">什么是 Serverless</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#serverless--ssr\">Serverless + SSR</a></li>\n<li>\n<p><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%B0%86-ssr-%E5%BA%94%E7%94%A8-serverless-%E5%8C%96%EF%BC%9F\">如何快速将 SSR 应用 Serverless 化？</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#1-%E5%88%9D%E5%A7%8B%E5%8C%96-nextjs-%E9%A1%B9%E7%9B%AE\">1. 初始化 Next.js 项目</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#2-%E5%85%A8%E5%B1%80%E5%AE%89%E8%A3%85-serverless-cli\">2. 全局安装 Serverless CLI</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#3-%E9%85%8D%E7%BD%AE-severlessyml\">3. 配置 severless.yml</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#4-%E9%83%A8%E7%BD%B2\">4. 部署</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E7%8E%B0%E6%9C%89-nextjs-%E5%BA%94%E7%94%A8%E8%BF%81%E7%A7%BB\">现有 Next.js 应用迁移</a></li>\n</ul>\n</li>\n<li>\n<p><a href=\"/best-practice/2020-06-10-ssr-yuga/#serverless-%E9%83%A8%E7%BD%B2%E6%96%B9%E6%A1%88%E7%9A%84%E4%BC%98%E5%8C%96\">Serverless 部署方案的优化</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E6%99%AE%E9%80%9A%E9%9D%92%E5%B9%B4%E5%81%9A%E6%B3%95\">普通青年做法</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E6%96%87%E8%89%BA%E9%9D%92%E5%B9%B4%E5%81%9A%E6%B3%95\">文艺青年做法</a></li>\n</ul>\n</li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#serverless--nextjs-%E9%83%A8%E7%BD%B2%E6%B5%81%E7%A8%8B%E5%9B%BE\">Serverless + Next.js 部署流程图</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90\">性能分析</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E9%A1%B5%E9%9D%A2%E8%AE%BF%E9%97%AE%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94\">页面访问性能对比</a></li>\n<li>\n<p><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E5%8E%8B%E6%B5%8B%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94\">压测性能对比</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#1%E5%93%8D%E5%BA%94%E6%97%B6%E9%97%B4%EF%BC%9A\">1.响应时间：</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#2tps\">2.TPS:</a></li>\n</ul>\n</li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E4%BB%B7%E6%A0%BC%E9%A2%84%E7%AE%97%E5%AF%B9%E6%AF%94\">价格预算对比</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E5%AF%B9%E6%AF%94%E5%88%86%E6%9E%90\">对比分析</a></li>\n<li><a href=\"/best-practice/2020-06-10-ssr-yuga/#%E6%9C%80%E5%90%8E\">最后</a></li>\n</ul>"},"previousBlog":{"id":"738f59df-7c1a-55e1-8356-2a054b8c9dc1","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020610/1591789849944-%E9%83%A8%E7%BD%B2.jpg","authors":["susu"],"categories":["best-practice"],"date":"2020-06-10T00:00:00.000Z","title":"腾讯云 Serverless 部署应用耗时降低了 73%","description":"压缩上传性能得到提升，控制台界面展示数据全面优化，Serverless 部署应用的过程体验更轻松、舒适。","authorslink":null,"translators":null,"translatorslink":null,"tags":["serverless","性能监控"],"keywords":"Serverless,Serverless 应用部署,Serverless Framework","outdated":null},"wordCount":{"words":99,"sentences":24,"paragraphs":24},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-06-10-serverless-deploy.md","fields":{"slug":"/best-practice/2020-06-10-serverless-deploy/","keywords":["next.js","nextjs","serverless","serverless","部署","优化","控制台","性能","上传","Serverless"]}},"nextBlog":{"id":"016de612-6f8f-5a7b-8b8a-bce228dca8cd","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/202069/1591692109083-%E6%A0%BC%E6%9E%97.png","authors":["陈新宇"],"categories":["best-practice"],"date":"2020-06-09T00:00:00.000Z","title":"Serverless 技术在格灵深瞳的落地实践","description":"使用腾讯云 Serverless 技术加速了格灵深瞳产品迭代，在验证原型方面效率和服务稳定性上提升了不少","authorslink":null,"translators":null,"translatorslink":null,"tags":["云函数","客户案例"],"keywords":"Serverless,scf,Serverless Framework","outdated":null},"wordCount":{"words":167,"sentences":22,"paragraphs":22},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-06-09-serverless-interview.md","fields":{"slug":"/best-practice/2020-06-09-serverless-interview/","keywords":["serverless","云函数","云原生","Serverless","方案","技术","serverless","服务","场景","运维","需求"]}}},"pageContext":{"isCreatedByStatefulCreatePages":false,"blogId":"235ebbe5-7f0a-524b-99a9-06860ace33fb","previousBlogId":"738f59df-7c1a-55e1-8356-2a054b8c9dc1","nextBlogId":"016de612-6f8f-5a7b-8b8a-bce228dca8cd"}}}