Tech系サービスやガジェットの使い心地、自分の作業環境、資産運用について気が向いたときに記録を残しています。

記事内のAmazonアソシエイト適格販売及び、Google Adsenseでお小遣いを得ています。

世界最速でHonoをAmplify Hostingで動かしてみた

前置き

筆者はAWSジャパンのソリューションアーキテクトです。本記事は個人的な興味関心による検証記事となります。

普段は常体で書いているこのブログですが、本記事は書きやすさから敬体で書いていきます。

AmplifyにおけるNext.js"以外"のSSRサポート

これまで、Next.jsのSSRのみをサポートしていましたが、Nuxt.jsのSSRサポートと同時に、node.jsで実行できる任意のフレームワークSSRアプリケーションを実行できるようになりました。

aws.amazon.com

ドキュメントには、Amplify Hosting用のディレクトリ構成が記載されており、ビルド成果物をこのディレクトリ構成に合わせて調整し、 deploy-manifest.json に設定を記載してやることで任意のエントリポイントからアプリケーションを起動できます。

※2023/11/21時点ではドキュメントは英語版のみ更新されています

docs.aws.amazon.com

Next.js および Nuxt.js 以外は公式アダプタが提供されておらず、この記事を書いている時点では自分でこれらの設定を記述する必要があるので、勉強がてら自分でアダプターを設定して作ってみました。

直近で一番利用頻度が高いフレームワークと言うことで、Astro SSRを試してみたかったところですが、エムスリーさんに先を越されてしまったため、別のフレームワークで試してみることにしました。

そこで、軽量かつ、エッジランタイムで爆速なWebフレームワーク、HonoをAmplify Hostingにデプロイしていきます。

github.com

なお、本記事で利用しているコードは以下のリポジトリで公開しています。

github.com

Hono Node.js Adapter

Honoといえば、専ら話題になるのはCloudflare Workers, Deno, Bunなどのエッジランタイムでの動作ですが、LambdaやLambda@Edgeをはじめとする一般的なnode環境でも使うことができます。

Amplify HostingのSSRはnode.jsで動いているように読めるので、今回はこのアダプターを使ってHonoプロジェクトを作成していきます。

ドキュメントのQuickStartに従ってプロジェクトを作成します。

npm create hono@latest
>nodejs

SSRであることがわかりやすくなるよう、ヘッダからUser-Agentを表示するロジックを追加して、localhostで動作確認します。

//src/index.ts
import { serve } from "@hono/node-server"
import { Hono } from "hono"

const app = new Hono()
app.get("/", c => {
  const userAgent = c.req.raw.headers.get("User-Agent")
  return c.text(`Hello Hono!\nUA: ${userAgent}`)
})

serve(app)

開発サーバーを起動した様子

うまくいきました。飾り気がないですが動作確認には十分なのでこのまま進めていきます。

Amplify Hosting用の設定ファイルを追加する

ドキュメントにExpressサーバー用のサンプルが記載されているので、これを参考にしながら設定していきます。

docs.aws.amazon.com

Amplify Hostingの設定については、ほぼエムスリーさんのブログの通りなので省略します。

www.m3tech.blog

たまたまローカルのnodeバージョンが18になっていたので、私は「ライブパッケージの更新」で Node.js version に18を設定しました。

tsxでentrypointを叩けるのか?

create hono で作成したサンプルはtsxを使って内部的にesbuildを使ったトランスパイルをしてTypescriptを実行しており、プロジェクトディレクトリに明示的にトランスパイル後のファイルが出てきません。

これは、感覚としてはTypeScriptをそのまま実行できており、開発者体験もよいので、「tsxでの実行をそのままAmplify Hostingに持ち込めたらいいなぁ~~!」くらいの気持ちで、ソースをそのまま.amplify-hostingディレクトリにコピーしてentrypointにindex.tsを指定してみました。

npm serveを自動的に認識していい感じに動いてくれればもうけものです。

//deploy-manifest.json
{
  "version": 1,
  "framework": { "name": "hono", "version": "3.10.2" },
  "imageSettings": {
    "sizes": [100, 200, 1920],
    "domains": [],
    "remotePatterns": [],
    "formats": [],
    "minimumCacheTTL": 60,
    "dangerouslyAllowSVG": false
  },
  "routes": [
    {
      "path": "/_amplify/image",
      "target": {
        "kind": "ImageOptimization",
        "cacheControl": "public, max-age=3600, immutable"
      }
    },
    {
      "path": "/*.*",
      "target": {
        "kind": "Static",
        "cacheControl": "public, max-age=2"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*",
      "target": {
        "kind": "Compute",
        "src": "default"
      }
    }
  ],
  "computeResources": [
    {
      "name": "default",
      "runtime": "nodejs18.x",
      "entrypoint": "index.ts" ←
    }
  ]
}

ドキュメント通りにpostbuild.shも作りますが、ビルドせずにソースをそのままコピーしてみます。

# postbuild.sh
#!/bin/bash

rm -rf ./.amplify-hosting

mkdir -p ./.amplify-hosting/compute

# 改変
cp -r ./src ./.amplify-hosting/compute/default 
cp -r ./node_modules ./.amplify-hosting/compute/default/node_modules

cp -r public ./.amplify-hosting/static

cp deploy-manifest.json ./.amplify-hosting/deploy-manifest.json

ビルドは成功したのですが、動きませんでした。そこまで都合よくはいきません…

jsファイルにトランスパイルする処理を追加していきます。

Honoのtsファイルをトランスパイルする

//tsconfig.json
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}
//package.json
{
  "type": "commonjs",
  "scripts": {
    "start": "tsx watch src/index.ts",
    "build": "tsc",
    "serve": "tsx watch src/index.ts",
    "postbuild": "chmod +x bin/postbuild.sh && ./bin/postbuild.sh"
  },
  "dependencies": {
    "@hono/node-server": "^1.2.3",
    "hono": "^3.10.2"
  },
  "devDependencies": {
    "@types/node": "^20.9.3",
    "tsx": "^3.12.2",
    "typescript": "^5.3.2"
  }
}

ビルドコマンドにtscを指定しつつ、ESmoduleとして認識されないように"type":"commonjs"を追加します。

tsconfigは、ドキュメントのものをそのままコピペして、postbuild.shdeploy-manifest.json もサンプル通りに修正しまして、無事デプロイが通りUser-Agentが表示されたことを確認出来ました。

オリジンの前にAmplifyがCloudfrontを挟んでアクセスさせているため、UAもCloudFrontと表示されています。