この記事の目標
- Nuxt.js + TypeScriptで実装
- Netlifyにホスティング
- Netlify FunctionsをTypeScriptで実装
- Nuxt.jsからNetlify Functionsを呼び出す
動作確認環境
| 項目 | バージョン | 
|---|---|
| macOS | 10.13.6 (High Sierra) | 
| node | 13.8.0 | 
| npm | 6.13.7 | 
ソースコード
1. Nuxt.js
まず、フロントエンド環境から構築していきます。
ボイラーテンプレート生成
create-nuxt-appを使います。
2020年3月18日にリリースされたv2.15.0から、TypeScriptをサポートしているので、質問に答えるだけで、簡単にTypeScript環境を構築できます。
$ npx create-nuxt-app nuxt-netlify-ts
- 言語はTypeScriptを選択
- TypeScriptのruntimeは@nuxt/typescript-runtimeを選択
- 
Axiosのモジュールを追加
- レンダリングモードはSPA
- あとはお好みで
? Project name nuxt-netlify-ts
? Project description My super Nuxt.js project
? Author name nishitaku
? Choose programming language TypeScript
? Choose the package manager Yarn
? Choose UI framework Vuetify.js
? Choose custom server framework None (Recommended)
? Choose the runtime for TypeScript @nuxt/typescript-runtime
? Choose Nuxt.js modules Axios, DotEnv
? Choose linting tools ESLint, Prettier
? Choose test framework None
? Choose rendering mode Single Page App
? Choose development tools jsconfig.json
以下Lintエラーが出力されますが、無視してOKです。
$ eslint --ext .js,.vue --ignore-path .gitignore . --fix
/Users/takuro/workspace/ojthv2/nuxt-netlify-ts/nuxt.config.js
  80:12  error  'config' is defined but never used. Allowed unused a
rgs must match /^_/u  @typescript-eslint/no-unused-vars
  80:20  error  'ctx' is defined but never used. Allowed unused args
 must match /^_/u     @typescript-eslint/no-unused-vars
✖ 2 problems (2 errors, 0 warnings)
error Command failed with exit code 1.
動作確認
開発サーバ起動
$ cd nuxt-netlify-ts
$ yarn dev
http://localhost:3000にアクセスして正常に表示されることを確認。

2. Netlifyにデプロイ
次に、生成したボイラーテンプレートを、Netlifyにデプロイします。
NetlifyはGitHubリポジトリと紐付けておくことで、PushやMergeをトリガーに、自動的にビルド&デプロイしてくれる機能があるため、それを使っていきます。
GitHubリポジトリ作成
Gitに関してはざっくりとコマンドのみ。
$ git remote add origin (リポジトリURL)
$ git add .
$ git commit -m "generated by create-nuxt-app/2.15.0 darwin-x64 node-v13.8.0"
$ git push origin master
NetlifyとGitHubリポジトリを紐付け
- Create a new site
- Continuous Deployment
- GitHubを選択
 
- Continuous Deployment: GitHub App
- 先程のGitHubリポジトリを選択
 
- Basic build settings
- Build command:yarn build
- Publish directory:dist
 
- Build command:
 
- Continuous Deployment
動作確認
デプロイ完了後、独自に割り当てられたhttps://(Netlifyドメイン)にブラウザからアクセスし、開発サーバのときと同じ画面が表示されることを確認。
3. Netlify Functions
Netlifyが提供するFaaS (Function as a Service)。
Freeプランで、125,000リクエスト/月、100時間/月使えます。
ベースはAWS Lambdaですが、シンプルな設定ですぐに使い始められます。
環境構築
netlify-lambdaを使います。
$ yarn add -D netlify-lambda
次に、TypeScriptで記述するためのライブラリを追加。
$ yarn add -D @babel/preset-typescript @types/aws-lambda
netlify-lambdaのbabelの設定を上書きするために、新たに.babelrcをルートに追加。
{
  "presets": [
    "@babel/preset-typescript",
    "@babel/preset-env"
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-object-assign",
    "@babel/plugin-proposal-object-rest-spread"
  ]
}
開発サーバを起動するためには、netlifyの設定ファイルが必要になります。
また、NetlifyはGitHubリポジトリにnetlify.tomlが存在する場合、GUIでの設定内容よりも優先します。そのため、Functionsの設定だけでなく、ホスティングの設定も合わせて記載しておく必要があります。
[build]
  Command = "yarn build"
  functions = "functions/dist"
  publish = "dist"
package.jsonに、開発サーバ起動スクリプトを追加。
{
  "scripts": {
    "dev:functions": "netlify-lambda serve functions/api",
  },
}
実装
ディレクトリfunctions/apiを追加し、サンプル用の関数を実装。
import { Handler, Context, Callback, APIGatewayEvent } from 'aws-lambda'
interface HelloResponse {
  statusCode: number
  body: string
}
export const handler: Handler = (
  event: APIGatewayEvent,
  context: Context,
  callback: Callback
) => {
  console.log(`hello invoked`)
  const params = event.queryStringParameters
  const response: HelloResponse = {
    statusCode: 200,
    body: JSON.stringify({
      msg: `Hello world ${Math.floor(Math.random() * 10)}`,
      requestId: context.awsRequestId || 'dummy',
      params
    })
  }
  callback(undefined, response)
}
動作確認
開発サーバを起動。9000番ポートで起動します。
$ yarn dev:functions
curlでレスポンスを確認。
$ curl "http://localhost:9000/.netlify/functions/hello"
{"msg":"Hello world 0","requestId":"dummy","params":{}}
因みに、http://localhost:9000/helloでも同様の結果が返ってきます。
4. Netlify Functionsのデプロイ
ホスティングと同様、GitHubリポジトリへPushするだけで、自動的にNetlify環境へ反映されます。
GitHubリポジトリへのPush
まず、Functionsのビルド設定を追加。
{
  "scripts": {
    "build": "netlify-lambda build functions/api && nuxt-ts build",
  },
}
GitHubにPushし、
動作確認
デプロイ完了後、curlでレスポンスを確認。
$ curl "http://(Netlifyドメイン)/.netlify/functions/hello"
{"msg":"Hello world 0","requestId":"dummy","params":{}}
5. Nuxt.jsからNetlify Functionsを呼び出す
NetlifyにデプロイしたNuxt.jsの画面から、Netlify Functionsの関数を呼び出します。
CORSエラー対応
開発環境では、Nuxt.jsのポート番号(3000)とnetlify-lambdaのポート番号(9000)が異なるため、そのままだとCORSエラーが発生します。
回避策として、@nuxtjs/proxyのリバースプロキシ機能を使うことで、http://localhost:3000/.netlify/functions/helloへのアクセスを、http://localhost:9000/.netlify/functions/helloへ転送します。
@nuxtjs/proxyをインストール。
$ yarn add @nuxtjs/proxy
nuxt.config.jsのmodulesに@nuxtjs/proxyを追加
export default {
  modules: [
    '@nuxtjs/proxy'
  ],
}
nuxt.config.jsのproxyを追加
export default {
  proxy: {
    '/.netlify/functions': {
      target: 'http://localhost:9000'
    }
  }
}
Axiosの設定
@nuxtjs/axiosはbaseURLに何も設定しなかった場合、デフォルトでhttp://localhost:3000となってしまいます。
そのため、https://(Netlifyドメイン)からhttps://(Netlifyドメイン)/.netlify/functionsを呼び出してほしいのに、http://localhost:3000/.netlify/functionsを呼び出してしまいます。
これを回避するため、nuxt.config.jsのaxiosのbaseURLを設定します。
export default {
  axios: [
    baseURL: '/'
  ],
}
ソースコード修正
動作確認用のコードをindex.vueに埋め込む。
<script>
export default {
  // 追加
  async mounted() {
    try {
      const response = await this.$axios.$get('.netlify/functions/hello')
      console.log(`respones=${JSON.stringify(response)}`)
    } catch (err) {
      console.error(err)
    }
  }
}
</script>
動作確認
$ yarn dev // Nuxt.js開発サーバ起動
$ yarn dev:functions // netlify-lambda開発サーバ起動
http://localhost:3000にブラウザからアクセスし、コンソールログにFunctionsを呼び出した結果が表示されていることを確認する。
さらに、GitHubにPushし、デプロイ完了後、ブラウザからNetlifyドメインへアクセスし、同様にコンソールログを確認する。
さいごに
**FaaS (Functions as as Service)**を使ったサーバレスアーキテクチャは、ここ数年でかなり使いやすくなってきている印象です。
今回のNetlify Functions以外にも
- Google Cloud Functions
- AWS Lambda
- IBM Cloud Functions
- Azure Functions
- Twilio Functions
 など、各クラウドベンダが提供しているFaaSもあるので、それらと比較してみると面白そうです。
また、Netlifyには他にも便利な機能があるみたいなので(Formsとか認証とか)、Netlify CLIと一緒に今後使ってみたいと思います。
Reference
- 【入門】Netlify Functionsコトハジメ
- Netlify Functions を使って CORS エラーを回避する
- [Nuxt.js & Netlify-Lambda で Serverless SPA 開発]
 (https://qiita.com/mikakane/items/b6d5bc7a472c61952302)
- Nuxtのaxios-moduleがbaseURLを決定する仕組みの解説と、Netlify FormsへPOSTするときの対策
- Nuxt.js (TypeScript) on Netlify、Functions / Split Testingで簡単にA/Bテストまでやってみる