✔️

Remix on Cloudflare Workers そして Remix on Hono

2023/09/05に公開

これなに

  • Remix on Cloudflare Workers始めたいな~とチュートリアルを探すと見つけられず、とりあえずのチュートリアルを自分向けに作りたかった。
  • せっかくRemixを動かすならどこかでみた、Honoのmount機能を試してみたかった。

1. Remix on Cloudflare Workers

create-remixでプロジェクトを作る。

npx create-remix@latest
Need to install the following packages:
  create-remix@1.19.3
Ok to proceed? (y) y

? Where would you like to create your app? ./my-remix-app
? What type of app do you want to create? Just the basics
? Where do you want to deploy? Choose Remix App Server if you're unsure; it's easy to change deployment targets. 
Cloudflare Workers
? TypeScript or JavaScript? TypeScript
? Do you want me to run `npm install`? Yes
  • Gitpodで作っていて、最初Cloudflareの認証周りでハマった。 Cloudflareは時限付きのAPIキーを払い出せるようなので、設定して解決した。
# install CLI
npm install -g wrangler@latest

# API KEY
export CLOUDFLARE_API_TOKEN=<API KEY>

# Deploy
wrangler deploy --name <name>

これだけでシンプルなページが公開できる。

2. Remix on Hono

NG Pattern

https://zenn.dev/yusukebe/articles/ee57dc12f34724#app.mount()

の記事にコードがあるので、my-remix-app/server.tsを書き換えるだけかと思ったら、ビルド時に Could not resolve "__STATIC_CONTENT_MANIFEST"エラーが出てしまった。

エラーメッセージで調べるとHonoのIssueが出てくるも、バグとかではなさそう。少なくともこれでは不十分みたいだ。
https://github.com/honojs/hono/issues/1127

Setting

よくよく調べるとyusukebe氏が動くパターンを公開しているので、これに倣ってcreate-remixで作った設定を変更していく。

https://github.com/yusukebe/remix-on-hono

package.json

エントリーポイントを指定する必要がありそう。ついでにminifyも入れる。

my-remix-app/package.json
{
  "private": true,
  "sideEffects": false,
  "type": "module",
  "scripts": {
    "build": "remix build",
-    "deploy": "wrangler deploy",
+    "deploy": "wrangler publish --minify server.ts",
    "dev": "remix dev --manual -c \"npm start\"",
    "start": "wrangler dev ./build/index.js",
    "typecheck": "tsc"
  },
  "dependencies": {
    "@cloudflare/kv-asset-handler": "^0.1.3",
    "@remix-run/cloudflare": "^1.19.3",
    "@remix-run/css-bundle": "^1.19.3",
    "@remix-run/react": "^1.19.3",
+   "hono": "^3.5.6",
    "isbot": "^3.6.8",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^3.19.0",
    "@remix-run/dev": "^1.19.3",
    "@remix-run/eslint-config": "^1.19.3",
    "@types/react": "^18.0.35",
    "@types/react-dom": "^18.0.11",
    "eslint": "^8.38.0",
    "typescript": "^5.0.4",
    "wrangler": "^3.1.1"
  },
  "engines": {
    "node": ">=16.13.0"
  }
}

remix.config.js

Service WorkerからModule Workerに変更しているという情報を見た気がするが、実のところこの辺は雰囲気でやっている(恥)

my-remix-app/remix.config.js
/** @type {import('@remix-run/dev').AppConfig} */
export default {
  ignoredRouteFiles: ["**/.*"],
-  server: "./server.ts",
-  serverConditions: ["workerd", "worker", "browser"],
+  serverConditions: ['worker'],
-  serverDependenciesToBundle: [
-    // bundle verything except the virtual module for the static content manifest provided by wrangler
-    /^(?!.*\b__STATIC_CONTENT_MANIFEST\b).*$/,
-  ],
+  serverDependenciesToBundle: 'all',
  serverMainFields: ["browser", "module", "main"],
  serverMinify: true,
  serverModuleFormat: "esm",
  serverPlatform: "neutral",
  // appDirectory: "app",
  // assetsBuildDirectory: "public/build",
  // serverBuildPath: "build/index.js",
  // publicPath: "/build/",
  future: {
    v2_dev: true,
    v2_errorBoundary: true,
    v2_headers: true,
    v2_meta: true,
    v2_normalizeFormMethod: true,
    v2_routeConvention: true,
  },
};

remix.env.d.ts

Workers KVへ格納される静的ファイルの型情報が__STATIC_CONTENT_MANIFESTらしい。今回は使わないので無効化してるのかな。

my-remix-app/remix.env.d.ts
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/cloudflare" />
/// <reference types="@cloudflare/workers-types" />
- 
- declare module "__STATIC_CONTENT_MANIFEST" {
-   const manifest: string;
-   export default manifest;
- }

Code

ただラップするだけでは芸がないので、以下のMiddlewareを入れつつ、シンプルに動かしてみる。

https://hono.dev/middleware/builtin/basic-auth

https://hono.dev/middleware/builtin/secure-headers

1. yusukebe/remix-on-hono方式

c.req.rawをhandlerに渡して、無事に動いた!

import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
import { secureHeaders } from 'hono/secure-headers'
import { createRequestHandler } from '@remix-run/cloudflare'
import * as build from './build'

// @ts-ignore
const handleRemixRequest = createRequestHandler(build, process.env.NODE_ENV)

const app = new Hono()

app.use(
  '*',
  basicAuth({
    username: 'hono',
    password: 'acoolproject',
  })
)
app.use('*', secureHeaders())

app.get('*', async (c) => {
  return await handleRemixRequest(c.req.raw)
})

export default app

2. app.mount

app.mountのドキュメントにはRemixの例がないが、色々見たら無事に動いた!

https://hono.dev/api/hono#mount


import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
import { secureHeaders } from 'hono/secure-headers'
import { createRequestHandler } from '@remix-run/cloudflare'
import * as build from './build'

// @ts-ignore
const remixHandler = createRequestHandler(build, process.env.NODE_ENV)

const app = new Hono(

app.use(
  '*',
  basicAuth({
    username: 'hono',
    password: 'acoolproject',
  })
)
app.use('*', secureHeaders())

app.mount('/', remixHandler)

export default app

3. mountできないケース?

app.mount('/remix', remixHandler)の様にしてCloudflare workersにデプロイすると、アクセス先でDDosっぽい動きになって困った。私の修正が間違っていそうな気がするが後日Issueを挙げておく。

おわりに

ということで基本的なBootstrapはできたので、必要に応じてRemix+Cloudflare Workersを使うのも怖くない! Honoでミドルウェアや値を渡すのもサクサクできそう。

調べて出てきたのだが、サードパーティのMiddlewareも見つけたので、コードを読んで良さそうならこれを選ぶのもあり。

https://github.com/sergiodxa/remix-hono

直接関係ないけど、とにかくCloudflare Workersのデプロイにかかる時間が短いので、トライ&エラーが苦にならないのが良いな~と感じました。

Discussion