{"componentChunkName":"component---src-templates-best-practice-detail-tsx","path":"/best-practice/2020-07-12-serverless-nextjs","result":{"data":{"currentBlog":{"id":"a95f844d-eae9-5465-b1e8-d5dcd4464453","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020713/1594610687799-Nextjs-93.jpg","authors":["Yugasun"],"categories":["best-practice"],"date":"2020-07-12T00:00:00.000Z","title":"如何优雅地部署一个 Serverless Next.js 应用","description":"本篇专门针对 Next.js 的 SSR 方案进行了探索和优化，一步一步带大家了解，如何基于 Serverless 架构部署一个实际的线上业务","authorslink":["https://github.com/yugasun"],"translators":null,"translatorslink":null,"tags":["Serverless SSR","Next.js"],"keywords":"Serverless SSR,Serverless Egg.js","outdated":null},"wordCount":{"words":478,"sentences":81,"paragraphs":81},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-07-12-serverless-nextjs.md","fields":{"slug":"/best-practice/2020-07-12-serverless-nextjs/","keywords":["nextjs","serverless","ssr","云函数","serverless","Next","部署","nextjs","静态","Serverless","Layer"]},"html":"<p>上一篇 <a href=\"https://serverlesscloud.cn/best-practice/2020-06-10-ssr-yuga\">前端福音：Serverless 和 SSR 的天作之合</a>，详细介绍了 SSR 相关知识，同时也提到了 Serverless 给 SSR 方案带来的福利。但它只是将 Next.js 应用部署到 Serverless 服务上而已，并不适合实际生产业务。为此本篇专门针对 Next.js 的 SSR 方案进行了探索和优化，一步一步带大家了解，如何基于 Serverless 架构部署一个实际的线上业务。</p>\n<blockquote>\n<p>抢先体验：<a href=\"https://cnode.yuga.chat\">serverless-cnode</a></p>\n</blockquote>\n<p>本文主要内容：</p>\n<ol>\n<li>如何快速部署 Serverless Next.js</li>\n<li>如何自定义 API 网关域名</li>\n<li>如何通过 COS 托管静态资源</li>\n<li>静态资源配置 CDN</li>\n<li>基于 Layer 部署 node_modules</li>\n</ol>\n<h2 id=\"如何快速部署-serverless-nextjs\"><a href=\"#%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2-serverless-nextjs\" 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>由于本人对 <a href=\"https://github.com/serverless/serverless\">Serverless Framework</a> 开发工具比较熟悉，并且长期参与相关开源工作，所以本文均使用 Serverless Components 方案进行部署，请在开始阅读本文之前，保证当前开发环境已经全局安装 <code class=\"language-text\">serverless</code> 命令行工具。\n本文依然上一篇中介绍的 <a href=\"https://github.com/serverless-components/tencent-nextjs\">Next.js 组件</a> 来帮助快速部署 Next.js 应用到腾讯云的 Serverless 服务上。</p>\n<p>我们先快速初始化一个 Serverless Next.js 项目：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"64081991429544780000\"\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 create -u https://github.com/serverless-components/tencent-nextjs/tree/master/example -p serverless-nextjs\n\\$ cd serverless-nextjs`, `64081991429544780000`)\"\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 create -u https://github.com/serverless-components/tencent-nextjs/tree/master/example -p serverless-nextjs\n$ <span class=\"token builtin class-name\">cd</span> serverless-nextjs</code></pre></div>\n<p>该项目模板已经默认配置好 <code class=\"language-text\">serverless.yml</code>，可以直接执行部署命令：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"54678483135946120000\"\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`, `54678483135946120000`)\"\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<p>大概 <code class=\"language-text\">30s</code> 左右就可以部署成功了，之后访问生成的 <code class=\"language-text\">apigw.url</code> 链接 <code class=\"language-text\">https://service-xxx-xxx.gz.apigw.tencentcs.com/release/</code> 就可以看到首页了。</p>\n<p>Next.js 组件，会默认帮助我们创建一个 <code class=\"language-text\">云函数</code> 和 <code class=\"language-text\">API 网关</code>，并且将它们关联，实际我们访问的 是 API 网关，然后触发云函数，来获得请求返回结果，流程图如下：</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020713/1594610891815-request-flow.png\" alt=\"Serverless Requst Flow\"></p>\n<blockquote>\n<p><strong>解释</strong>：我们在执行部署命令时，由于一个简单的 Next.js 应用除了业务代码，还包括庞大的 <code class=\"language-text\">node_modules</code> 文件夹，这就导致打包压缩的代码体积大概 <code class=\"language-text\">20M</code> 左右，所以大部分时间消耗在代码上传上。这里的速度也跟开发环境的网络环境有关，而实际上我们云端部署是很快的，这也是为什么需要 <code class=\"language-text\">30s</code> 左右的部署时间，而且网络差时会更久，当然后面也会提到如何提高部署速度。</p>\n</blockquote>\n<p>相信你已经体会到，借助 Serverless Components 解决方案的便利，它确实可以帮助我们的应用高效的部署到云端。而且这里使用的 Next.js 组件，针对代码上传也做了很多优化工作，来保证快速的部署效率。</p>\n<p>接下来将介绍如何基于 Next.js 组件，进一步优化我们的部署体验。</p>\n<h2 id=\"如何自定义-api-网关域名\"><a href=\"#%E5%A6%82%E4%BD%95%E8%87%AA%E5%AE%9A%E4%B9%89-api-%E7%BD%91%E5%85%B3%E5%9F%9F%E5%90%8D\" aria-label=\"如何自定义 api 网关域名 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>如何自定义 API 网关域名</h2>\n<p>使用过 API 网关的小伙伴，应该都知道它可以配置自定义域名，如下图所示：</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020713/1594610915639-manual-config-custom-domain.png\" alt=\"Manual Config Custom Domain\"></p>\n<p>但是这个手动配置还是不够方便，为此 Next.js 组件也提供了 <code class=\"language-text\">customDomains</code> 来帮助开发者快速配置自定义域名，于是我们可以在项目的 <code class=\"language-text\">serverless.yml</code> 中新增如下配置：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"91514326210702150000\"\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    dist: ./\n    hook: npm run build\n    exclude:\n      - .env\n  region: ap-guangzhou\n  runtime: Nodejs10.15\n  apigatewayConf:\n    protocols:\n      - https\n    environment: release\n    enableCORS: true\n    # 自定义域名相关配置\n    customDomains:\n      - domain: test.yuga.chat\n        certificateId: abcdefg # 证书 ID\n        # 这里将 API 网关的 release 环境映射到根路径\n        pathMappingSet:\n          - path: /\n            environment: release\n        protocols:\n          - https`, `91514326210702150000`)\"\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\">dist</span><span class=\"token punctuation\">:</span> ./\n    <span class=\"token key atrule\">hook</span><span class=\"token punctuation\">:</span> npm run build\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\">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\">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\n    <span class=\"token key atrule\">enableCORS</span><span class=\"token punctuation\">:</span> <span class=\"token boolean important\">true</span>\n    <span class=\"token comment\"># 自定义域名相关配置</span>\n    <span class=\"token key atrule\">customDomains</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">domain</span><span class=\"token punctuation\">:</span> test.yuga.chat\n        <span class=\"token key atrule\">certificateId</span><span class=\"token punctuation\">:</span> abcdefg <span class=\"token comment\"># 证书 ID</span>\n        <span class=\"token comment\"># 这里将 API 网关的 release 环境映射到根路径</span>\n        <span class=\"token key atrule\">pathMappingSet</span><span class=\"token punctuation\">:</span>\n          <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">path</span><span class=\"token punctuation\">:</span> /\n            <span class=\"token key atrule\">environment</span><span class=\"token punctuation\">:</span> release\n        <span class=\"token key atrule\">protocols</span><span class=\"token punctuation\">:</span>\n          <span class=\"token punctuation\">-</span> https</code></pre></div>\n<p>由于这里使用的是 <code class=\"language-text\">https</code> 协议，所以需要配置托管在腾讯云服务的证书 ID，可以到 <a href=\"https://console.cloud.tencent.com/ssl\">SSL 证书控制台</a> 查看。腾讯云已经提供了申请免费证书的功能，当然你也可以上传自己的证书进行托管。</p>\n<p>之后我们再次执行部署命令，会得到如下输出结果：</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020713/1594610931609-custom-domain-outputs.png\" alt=\"Custom Domain Outputs\"></p>\n<p>这里由于自定义域名时通过 CNAME 映射到 API 网关服务，所以还需要手动添加输出结果中红框部分的 CNAME 解析记录。等待自定义域名解析成功，就可以正常访问了。</p>\n<h2 id=\"如何通过-cos-托管静态资源\"><a href=\"#%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87-cos-%E6%89%98%E7%AE%A1%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90\" aria-label=\"如何通过 cos 托管静态资源 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>如何通过 COS 托管静态资源</h2>\n<p>Next.js 应用，有两种静态资源：</p>\n<ol>\n<li>项目中通过资源引入的方式使用，这种会经过 <code class=\"language-text\">Webpack</code> 打包处理输出到 <code class=\"language-text\">.next/static</code> 目录，比如 <code class=\"language-text\">.next/static/css</code> 样式文件目录。</li>\n<li>直接放到项目根目录的 <code class=\"language-text\">public</code> 文件夹，通过静态文件服务返回，然后项目中可以直接通过 url 的方式引入（<a href=\"https://nextjs.org/docs/basic-features/static-file-serving\">官方介绍</a>）。</li>\n</ol>\n<p>第一种的资源很好处理，Next.js 框架直接支持在 <code class=\"language-text\">next.config.js</code> 中配置 <code class=\"language-text\">assetPrefix</code> 来帮助我们在构建项目时，将提供静态资源托管服务的访问 url 添加到静态资源引入前缀中。如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"93214506923022600000\"\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(`// next.config.js\nconst isProd = process.env.NODE_ENV === &quot;production&quot;;\nconst STATIC_URL =\n  &quot;https://serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com&quot;;\nmodule.exports = {\n  assetPrefix: isProd ? STATIC_URL : &quot;&quot;,\n};`, `93214506923022600000`)\"\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 comment\">// next.config.js</span>\n<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>\n<span class=\"token keyword\">const</span> <span class=\"token constant\">STATIC_URL</span> <span class=\"token operator\">=</span>\n  <span class=\"token string\">\"https://serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com\"</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 <span class=\"token operator\">?</span> <span class=\"token constant\">STATIC_URL</span> <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<p>上面配置中的 <code class=\"language-text\">STATIC_URL</code> 就是静态资源托管服务提供的访问 url，示例中是腾讯云对应的 COS 访问 url。</p>\n<p>那么针对第二种资源我们如何处理呢？这里就需要对业务代码进行稍微改造了。</p>\n<p>首先，需要在 <code class=\"language-text\">next.config.js</code> 中添加 <code class=\"language-text\">env.STATIC_URL</code> 环境变量:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"70911558863059090000\"\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 === &quot;production&quot;;\nconst STATIC_URL =\n  &quot;https://serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com&quot;;\nmodule.exports = {\n  env: {\n    // 3000 为本地开发时的端口，这里是为了本地开发时，也可以正常运行\n    STATIC_URL: isProd ? STATIC_URL : &quot;http://localhost:3000&quot;,\n  },\n  assetPrefix: isProd ? STATIC_URL : &quot;&quot;,\n};`, `70911558863059090000`)\"\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>\n<span class=\"token keyword\">const</span> <span class=\"token constant\">STATIC_URL</span> <span class=\"token operator\">=</span>\n  <span class=\"token string\">\"https://serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com\"</span><span class=\"token punctuation\">;</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  env<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// 3000 为本地开发时的端口，这里是为了本地开发时，也可以正常运行</span>\n    <span class=\"token constant\">STATIC_URL</span><span class=\"token punctuation\">:</span> isProd <span class=\"token operator\">?</span> <span class=\"token constant\">STATIC_URL</span> <span class=\"token punctuation\">:</span> <span class=\"token string\">\"http://localhost:3000\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  assetPrefix<span class=\"token punctuation\">:</span> isProd <span class=\"token operator\">?</span> <span class=\"token constant\">STATIC_URL</span> <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<p>然后，在项目中修改引入 <code class=\"language-text\">public</code> 中静态资源的路径，比如：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"6531891794941647000\"\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(`<!-- before -->\n<head>\n  <title>Create Next App</title>\n  <link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; />\n</head>\n\n<!-- after -->\n<head>\n  <title>Create Next App</title>\n  <link rel=&quot;icon&quot; href={\\`\\${process.env.STATIC_URL}/favicon.ico\\`} />\n</head>`, `6531891794941647000`)\"\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=\"html\"><pre class=\"language-html\"><code class=\"language-html\"><span class=\"token comment\">&lt;!-- before --></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>head</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>title</span><span class=\"token punctuation\">></span></span>Create Next App<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>title</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>link</span> <span class=\"token attr-name\">rel</span><span class=\"token attr-value\"><span class=\"token punctuation\">=</span><span class=\"token punctuation\">\"</span>icon<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation\">=</span><span class=\"token punctuation\">\"</span>/favicon.ico<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>head</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token comment\">&lt;!-- after --></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>head</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>title</span><span class=\"token punctuation\">></span></span>Create Next App<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>title</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>link</span> <span class=\"token attr-name\">rel</span><span class=\"token attr-value\"><span class=\"token punctuation\">=</span><span class=\"token punctuation\">\"</span>icon<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation\">=</span>{`${process.env.STATIC_URL}/favicon.ico`}</span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>head</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>最后，在 <code class=\"language-text\">serverless.yml</code> 中新增静态资源相关配置 <code class=\"language-text\">staticConf</code>，如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"88506882707197940000\"\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    dist: ./\n    hook: npm run build\n    exclude:\n      - .env\n  region: ap-guangzhou\n  runtime: Nodejs10.15\n  apigatewayConf:\n    # 此处省略....\n  # 静态资源相关配置\n  staticConf:\n    cosConf:\n      # 这里是创建的 COS 桶名称\n      bucket: serverless-nextjs`, `88506882707197940000`)\"\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\">dist</span><span class=\"token punctuation\">:</span> ./\n    <span class=\"token key atrule\">hook</span><span class=\"token punctuation\">:</span> npm run build\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\">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\">apigatewayConf</span><span class=\"token punctuation\">:</span>\n    <span class=\"token comment\"># 此处省略....</span>\n  <span class=\"token comment\"># 静态资源相关配置</span>\n  <span class=\"token key atrule\">staticConf</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">cosConf</span><span class=\"token punctuation\">:</span>\n      <span class=\"token comment\"># 这里是创建的 COS 桶名称</span>\n      <span class=\"token key atrule\">bucket</span><span class=\"token punctuation\">:</span> serverless<span class=\"token punctuation\">-</span>nextjs</code></pre></div>\n<p>通过配置 <code class=\"language-text\">staticConf.cosConf</code> 指定 COS 桶，执行部署时，会默认自动将编译生成的 <code class=\"language-text\">.next</code> 和 <code class=\"language-text\">public</code> 文件夹静态资源上传到指定的 COS。</p>\n<p>修改好配置后，再次执行 <code class=\"language-text\">serverless deploy</code> 进行部署：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"98618710299581280000\"\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\n\nserverless ⚡framework\nAction: &quot;deploy&quot; - Stage: &quot;dev&quot; - App: &quot;appDemo&quot; - Instance: &quot;nextjsDemo&quot;\n\nregion:    ap-guangzhou\n# 此处省略......\nstaticConf:\n  cos:\n    region:    ap-guangzhou\n    cosOrigin: serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com\n    bucket:    serverless-nextjs-xxx`, `98618710299581280000`)\"\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\n\nserverless ⚡framework\nAction: <span class=\"token string\">\"deploy\"</span> - Stage: <span class=\"token string\">\"dev\"</span> - App: <span class=\"token string\">\"appDemo\"</span> - Instance: <span class=\"token string\">\"nextjsDemo\"</span>\n\nregion:    ap-guangzhou\n<span class=\"token comment\"># 此处省略......</span>\nstaticConf:\n  cos:\n    region:    ap-guangzhou\n    cosOrigin: serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com\n    bucket:    serverless-nextjs-xxx</code></pre></div>\n<p>浏览器访问，打开调试控制台，可以看到访问的静态资源请求路径如下：</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020713/1594610953536-static-asset-url.png\" alt=\"Static Asset Url\"></p>\n<p>上图可以看出，静态资源均通过访问 COS 获取，现在云函数只需要渲染入口文件，而不需要像之前，静态资源全部通过云函数返回。</p>\n<blockquote>\n<p>备注：之前由于都是将 .next 部署到了云函数，所以没法访问页面后，页面中的静态资源，如图片，都需要再次访问云函数，然后获取。于是看似我们请求了一次云函数，而实际上云函数单位时间并发数，会根据页面静态资源请求数而增加，从而造成冷启动问题。</p>\n</blockquote>\n<h2 id=\"静态资源配置-cdn\"><a href=\"#%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E9%85%8D%E7%BD%AE-cdn\" aria-label=\"静态资源配置 cdn 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>静态资源配置 CDN</h2>\n<p>上面我们已经将静态资源都部署到 COS 了，页面访问也快了很多。但是对于生产环境，还需要给静态资源配置 CDN 的。通过 COS 控制台已经可以很方便的配置 CDN 加速域名了。但是还是需要手动去配置，作为一名懒惰的程序员，我还是不能接受的。 而 Next.js 组件正好提供了给静态资源配置 CDN 的能力，只需要在 <code class=\"language-text\">serverless.yml</code> 中新增 <code class=\"language-text\">staticConf.cdnConf</code> 配置即可，如下所示：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"6969559669848491000\"\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(`# 此处省略....\ninputs:\n  # 此处省略....\n\n  # 静态资源相关配置\n  staticConf:\n    cosConf:\n      # 这里是创建的 COS 桶名称\n      bucket: serverless-nextjs\n    cdnConf:\n      domain: static.test.yuga.chat\n      https:\n        certId: abcdefg`, `6969559669848491000`)\"\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 comment\"># 此处省略....</span>\n<span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token comment\"># 此处省略....</span>\n\n  <span class=\"token comment\"># 静态资源相关配置</span>\n  <span class=\"token key atrule\">staticConf</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">cosConf</span><span class=\"token punctuation\">:</span>\n      <span class=\"token comment\"># 这里是创建的 COS 桶名称</span>\n      <span class=\"token key atrule\">bucket</span><span class=\"token punctuation\">:</span> serverless<span class=\"token punctuation\">-</span>nextjs\n    <span class=\"token key atrule\">cdnConf</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">domain</span><span class=\"token punctuation\">:</span> static.test.yuga.chat\n      <span class=\"token key atrule\">https</span><span class=\"token punctuation\">:</span>\n        <span class=\"token key atrule\">certId</span><span class=\"token punctuation\">:</span> abcdefg</code></pre></div>\n<p>这里使用 <code class=\"language-text\">https</code> 协议，所以也添加了 <code class=\"language-text\">https</code> 的 <code class=\"language-text\">certId</code> 证书 ID 配置。此外静态资源域名也需要修改为 CDN 域名，修改 <code class=\"language-text\">next.config.js</code> 如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"1482212047431463700\"\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 === &quot;production&quot;;\nconst STATIC_URL = &quot;https://static.test.yuga.chat&quot;;\nmodule.exports = {\n  env: {\n    STATIC_URL: isProd ? STATIC_URL : &quot;http://localhost:3000&quot;,\n  },\n  assetPrefix: isProd ? STATIC_URL : &quot;&quot;,\n};`, `1482212047431463700`)\"\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>\n<span class=\"token keyword\">const</span> <span class=\"token constant\">STATIC_URL</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"https://static.test.yuga.chat\"</span><span class=\"token punctuation\">;</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  env<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token constant\">STATIC_URL</span><span class=\"token punctuation\">:</span> isProd <span class=\"token operator\">?</span> <span class=\"token constant\">STATIC_URL</span> <span class=\"token punctuation\">:</span> <span class=\"token string\">\"http://localhost:3000\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  assetPrefix<span class=\"token punctuation\">:</span> isProd <span class=\"token operator\">?</span> <span class=\"token constant\">STATIC_URL</span> <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<p>配置好后，再次执行部署，结果如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"88379014508224610000\"\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\n\nserverless ⚡framework\nAction: &quot;deploy&quot; - Stage: &quot;dev&quot; - App: &quot;appDemo&quot; - Instance: &quot;nextjsDemo&quot;\n\nregion:    ap-guangzhou\napigw:\n  # 省略...\nscf:\n  # 省略...\nstaticConf:\n  cos:\n    region:    ap-guangzhou\n    cosOrigin: serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com\n    bucket:    serverless-nextjs-xxx\n  cdn:\n    domain: static.test.yuga.chat\n    url:    https://static.test.yuga.chat`, `88379014508224610000`)\"\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\n\nserverless ⚡framework\nAction: <span class=\"token string\">\"deploy\"</span> - Stage: <span class=\"token string\">\"dev\"</span> - App: <span class=\"token string\">\"appDemo\"</span> - Instance: <span class=\"token string\">\"nextjsDemo\"</span>\n\nregion:    ap-guangzhou\napigw:\n  <span class=\"token comment\"># 省略...</span>\nscf:\n  <span class=\"token comment\"># 省略...</span>\nstaticConf:\n  cos:\n    region:    ap-guangzhou\n    cosOrigin: serverless-nextjs-xxx.cos.ap-guangzhou.myqcloud.com\n    bucket:    serverless-nextjs-xxx\n  cdn:\n    domain: static.test.yuga.chat\n    url:    https://static.test.yuga.chat</code></pre></div>\n<blockquote>\n<p>注意：这里虽然添加了 CDN 域名，但是还是需要手动配置 CNAME <code class=\"language-text\">static.test.yuga.chat.cdn.dnsv1.com</code> 解析记录。</p>\n</blockquote>\n<h2 id=\"优化前后对比\"><a href=\"#%E4%BC%98%E5%8C%96%E5%89%8D%E5%90%8E%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>到这里，Serverless Next.js 应用体验已经优化了很多，我们可以使用 <code class=\"language-text\">Lighthouse</code> 进行性能测试，来验证下我们的收获。测试结果如下：</p>\n<p>优化前：</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020713/1594610973460-nextjs-optimize-before.png\" alt=\"Before Next.js Optimization\"></p>\n<p>优化后：</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020713/1594610976855-nextjs-optimize-after.png\" alt=\"After Next.js Optimization\"></p>\n<p>前后对比，可以明显看出优化效果，当然这里主要是针对静态资源进行了优化处理，减少了冷启动。为了更好地游湖体验，我们还可以做的更多，这里就不展开讨论了。</p>\n<h2 id=\"基于-layer-部署-node_modules\"><a href=\"#%E5%9F%BA%E4%BA%8E-layer-%E9%83%A8%E7%BD%B2-node_modules\" aria-label=\"基于 layer 部署 node_modules 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>基于 Layer 部署 node_modules</h2>\n<p>随着我们的业务变得复杂，项目体积会越来越大，node<em>modules 文件夹也会变得原来越大，而现在每次部署都需要将 node</em>modules 打包压缩，然后上传，跟业务代码一起部署到云函数。在实际开发中， <code class=\"language-text\">node_modules</code> 大部分时候是不怎么变化的，但是当前每次都需要上传，这必然会浪费很多部署时间，尤其在网络状态不好的情况下，代码上传就更慢了。</p>\n<p>既然 <code class=\"language-text\">node_modules</code> 文件夹是不怎么变更的，那么我们能不能只有在它变化时才上传更新呢？</p>\n<p>借助 <a href=\"https://cloud.tencent.com/document/product/583/40159\">Layer</a> 的能力是可以实现的。</p>\n<p>在这之前，先简单介绍下 Layer:</p>\n<blockquote>\n<p>借助 Layer，可以将项目依赖放在 Layer 中而无需部署到云函数代码中。函数在执行前，会先加载 Layer 中的文件到 <code class=\"language-text\">/opt</code> 目录下（云函数代码会挂载到 <code class=\"language-text\">/var/user/</code> 目录下），同时会将 <code class=\"language-text\">/opt</code> 和 <code class=\"language-text\">/opt/node_modules</code> 添加到 <code class=\"language-text\">NODE_PATH</code> 中，这样即使云函数中没有 <code class=\"language-text\">node_modules</code> 文件夹，也可以通过 <code class=\"language-text\">require(&#39;abc&#39;)</code> 方式引入使用该模块。</p>\n</blockquote>\n<p>正好 <a href=\"https://github.com/serverless-components/tencent-layer\">Layer 组件</a> 可以帮助我们自动创建 <code class=\"language-text\">Layer</code>。</p>\n<p>使用时只需要在项目下添加 <code class=\"language-text\">layer</code> 文件夹，并且创建 <code class=\"language-text\">layer/serverless.yml</code> 配置如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"42300265701611090000\"\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: layer\nname: nextjsDemo-layer\n\ninputs:\n  region: ap-guangzhou\n  name: \\${name}\n  src: ../node_modules\n  runtimes:\n    - Nodejs10.15\n    - Nodejs12.16`, `42300265701611090000`)\"\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> layer\n<span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> nextjsDemo<span class=\"token punctuation\">-</span>layer\n\n<span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\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\">name</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span>name<span class=\"token punctuation\">}</span>\n  <span class=\"token key atrule\">src</span><span class=\"token punctuation\">:</span> ../node_modules\n  <span class=\"token key atrule\">runtimes</span><span class=\"token punctuation\">:</span>\n    <span class=\"token punctuation\">-</span> Nodejs10.15\n    <span class=\"token punctuation\">-</span> Nodejs12.16</code></pre></div>\n<p>配置说明:</p>\n<blockquote>\n<p><strong>region</strong>：地区，需要跟云函数保持一致\n<strong>name</strong>：Layer 名称，在云函数绑定指定 Layer 时需要指定\n<strong>src</strong>：指定需要上传部署到 Layer 的目录\n<strong>runtimes</strong>：支持的云函数运行环境</p>\n</blockquote>\n<p>执行部署 Layer 命令:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"51888191509827396000\"\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 --target=./layer\n\nserverless ⚡framework\nAction: &quot;deploy&quot; - Stage: &quot;dev&quot; - App: &quot;appDemo&quot; - Instance: &quot;nextjsDemo-layer&quot;\n\nregion:      ap-guangzhou\nname:        nextjsDemo-layer\nbucket:      sls-layer-ap-guangzhou-code\nobject:      nextjsDemo-layer-1594356915.zip\ndescription: Layer created by serverless component\nruntimes:\n  - Nodejs10.15\n  - Nodejs12.16\nversion:     1`, `51888191509827396000`)\"\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 --target<span class=\"token operator\">=</span>./layer\n\nserverless ⚡framework\nAction: <span class=\"token string\">\"deploy\"</span> - Stage: <span class=\"token string\">\"dev\"</span> - App: <span class=\"token string\">\"appDemo\"</span> - Instance: <span class=\"token string\">\"nextjsDemo-layer\"</span>\n\nregion:      ap-guangzhou\nname:        nextjsDemo-layer\nbucket:      sls-layer-ap-guangzhou-code\nobject:      nextjsDemo-layer-1594356915.zip\ndescription: Layer created by serverless component\nruntimes:\n  - Nodejs10.15\n  - Nodejs12.16\nversion:     <span class=\"token number\">1</span></code></pre></div>\n<p>从输出可以清晰看到 Layer 组件已经帮助我们自动创建了一个名称为 <code class=\"language-text\">nextjsDemo-layer</code>，版本为 <code class=\"language-text\">1</code> 的 Layer。</p>\n<p>接下来我们如何自动和我们的 Next.js 云函数绑定呢？</p>\n<p>参考 <a href=\"https://github.com/serverless/components#outputs\">serverless components outputs 说明文档</a> ，可以通过引用一个基于 Serverless Components 部署成功的实例的 <code class=\"language-text\">outputs</code> (这里就是控制台输出对象内容)，语法如下：</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\"># Syntax\n${output:[stage]:[app]:[instance].[output]}</code></pre></div>\n<p>那么我们只需要在项目根目录的 <code class=\"language-text\">serverless.yml</code> 文件中，添加 <code class=\"language-text\">layers</code> 配置就可以了：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"84711542775371200000\"\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    dist: ./\n    hook: npm run build\n    exclude:\n      - .env\n      - &quot;node_modules/**&quot;\n  region: ap-guangzhou\n  runtime: Nodejs10.15\n  layers:\n    - name: \\${output:\\${stage}:\\${app}:\\${name}-layer.name}\n      version: \\${output:\\${stage}:\\${app}:\\${name}-layer.version}\n  # 静态资源相关配置\n  # 此处省略....`, `84711542775371200000`)\"\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\">dist</span><span class=\"token punctuation\">:</span> ./\n    <span class=\"token key atrule\">hook</span><span class=\"token punctuation\">:</span> npm run build\n    <span class=\"token key atrule\">exclude</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> .env\n      <span class=\"token punctuation\">-</span> <span class=\"token string\">\"node_modules/**\"</span>\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\">layers</span><span class=\"token punctuation\">:</span>\n    <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span>output<span class=\"token punctuation\">:</span>$<span class=\"token punctuation\">{</span>stage<span class=\"token punctuation\">}</span><span class=\"token punctuation\">:</span>$<span class=\"token punctuation\">{</span>app<span class=\"token punctuation\">}</span><span class=\"token punctuation\">:</span>$<span class=\"token punctuation\">{</span>name<span class=\"token punctuation\">}</span><span class=\"token punctuation\">-</span>layer.name<span class=\"token punctuation\">}</span>\n      <span class=\"token key atrule\">version</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span>output<span class=\"token punctuation\">:</span>$<span class=\"token punctuation\">{</span>stage<span class=\"token punctuation\">}</span><span class=\"token punctuation\">:</span>$<span class=\"token punctuation\">{</span>app<span class=\"token punctuation\">}</span><span class=\"token punctuation\">:</span>$<span class=\"token punctuation\">{</span>name<span class=\"token punctuation\">}</span><span class=\"token punctuation\">-</span>layer.version<span class=\"token punctuation\">}</span>\n  <span class=\"token comment\"># 静态资源相关配置</span>\n  <span class=\"token comment\"># 此处省略....</span></code></pre></div>\n<blockquote>\n<p>注意：不同组件部署实例结果的依赖使用，需要保证 serverless.yml 中 <code class=\"language-text\">org,app,stage</code> 三个配置是一致的。</p>\n</blockquote>\n<p>由于 <code class=\"language-text\">node_modules</code> 已经通过 Layer 部署，所以还需要在 <code class=\"language-text\">src.exclude</code> 中添加忽略部署该文件夹。</p>\n<p>之后再次执行部署命令 <code class=\"language-text\">serverless deploy</code> 即可， 你会发现这次部署时间大大缩减了，因为我们不在需要每次压缩上传 <code class=\"language-text\">node_moduels</code> 这个庞大的文件夹了 (<em>^▽^</em>)</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>基于以上方案，我部署了一个完整的 Cnode 项目，<a href=\"https://github.com/serverless-plus/serverless-cnode\">serverless-cnode</a>，欢迎感兴趣的小伙伴，提交宝贵的 ISSUE/PR。</p>\n<p>关于 Serverless SSR 的方案，我也在不断尝试和探索中，如果你有更好的方案和建议，欢迎评论或者私信来撩~</p>\n<hr>\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-07-12-serverless-nextjs/#%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2-serverless-nextjs\">如何快速部署 Serverless Next.js</a></li>\n<li><a href=\"/best-practice/2020-07-12-serverless-nextjs/#%E5%A6%82%E4%BD%95%E8%87%AA%E5%AE%9A%E4%B9%89-api-%E7%BD%91%E5%85%B3%E5%9F%9F%E5%90%8D\">如何自定义 API 网关域名</a></li>\n<li><a href=\"/best-practice/2020-07-12-serverless-nextjs/#%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87-cos-%E6%89%98%E7%AE%A1%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90\">如何通过 COS 托管静态资源</a></li>\n<li><a href=\"/best-practice/2020-07-12-serverless-nextjs/#%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E9%85%8D%E7%BD%AE-cdn\">静态资源配置 CDN</a></li>\n<li><a href=\"/best-practice/2020-07-12-serverless-nextjs/#%E4%BC%98%E5%8C%96%E5%89%8D%E5%90%8E%E5%AF%B9%E6%AF%94\">优化前后对比</a></li>\n<li><a href=\"/best-practice/2020-07-12-serverless-nextjs/#%E5%9F%BA%E4%BA%8E-layer-%E9%83%A8%E7%BD%B2-node_modules\">基于 Layer 部署 node_modules</a></li>\n<li><a href=\"/best-practice/2020-07-12-serverless-nextjs/#%E6%9C%80%E5%90%8E\">最后</a></li>\n</ul>"},"previousBlog":{"id":"ca884fd4-891f-599b-ac9f-b6f1169df802","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020720/1595246883106-%E5%B0%81%E9%9D%A2%E5%9B%BE.jpg","authors":["杜佳辰"],"categories":["best-practice"],"date":"2020-07-16T00:00:00.000Z","title":"通过 Serverless Regsitry 快速开发与部署一个 WordCount 实例","description":"本文介绍了如何通过 Registry 开发与部署一个项目模版","authorslink":["https://github.com/Jiachen0417"],"translators":null,"translatorslink":null,"tags":["Registry","MapReduce"],"keywords":"Serverless Registry,MapReduce","outdated":null},"wordCount":{"words":182,"sentences":46,"paragraphs":46},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-07-16-registry-mapreduce.md","fields":{"slug":"/best-practice/2020-07-16-registry-mapreduce/","keywords":["serverless","spa","云函数","serverless","cos","模版","MapReduce","yml","srcmr"]}},"nextBlog":{"id":"ac7f04ed-9f64-581f-b574-b0d5529a61d7","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/202072/1593676142784-http.jpg","authors":["Yugasun"],"categories":["best-practice"],"date":"2020-07-01T00:00:00.000Z","title":"如何将 Web 框架迁移到 Serverless","description":"传统 Web 服务想迁移到 Serverless 上，需要进行相关改造和特殊处理的。本文将具体帮助大家剖析下，如何 Serverless 化传统的 Web 服务","authorslink":["https://github.com/yugasun"],"translators":null,"translatorslink":null,"tags":["Serverless","Web"],"keywords":"Serverless SSR,Serverless Egg.js","outdated":null},"wordCount":{"words":490,"sentences":89,"paragraphs":88},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-07-01-serverless-http.md","fields":{"slug":"/best-practice/2020-07-01-serverless-http/","keywords":["nodejs","serverless","云函数","Serverless","Web","server","serverless","http","Express","服务"]}}},"pageContext":{"isCreatedByStatefulCreatePages":false,"blogId":"a95f844d-eae9-5465-b1e8-d5dcd4464453","previousBlogId":"ca884fd4-891f-599b-ac9f-b6f1169df802","nextBlogId":"ac7f04ed-9f64-581f-b574-b0d5529a61d7"}}}