水曜日, 8月 20, 2025
水曜日, 8月 20, 2025
- Advertisment -
ホームニューステックニュースNext.js on Cloudflare Workersでフルスタックアプリケーションを開発した

Next.js on Cloudflare Workersでフルスタックアプリケーションを開発した



始まり始まり

記事なんて書くだけで偉いですからね、もし僕がこの1行だけ書いて保存してアップロードしたところで、それはそれで現代アートみたいで「うーむ深い」という人がいると思いますが、早起きしたので続きを書きます。

今回は約4日でフルスタックのアプリケーションを作りました

AIがインタビューして記事を作成してくれるサービスで、Twitterのコミュニティでのお題に合わせて開発したということと、1年前に購入したOpen AIのクレジットが期限切れになるのでとにかくAPIを使うためのものを作りたかったという背景があります。

https://thisis.ooo/

自分の考えをアウトプットするぞ!ってなると意外と「あれ、何が書きたいんだっけ」とか、「うーんななんか違う」という感じで結局何も書かないことってあると思うのですが、でも不思議、誰かに話すときはスラスラ話せたりするんですよね、ということで、アウトプットのハードルを下げるためには「インタビュー」というものが有効だと思いました。しかも相手がAIだと否定もされないのでとてもやりやすいですね。良かったら試してみてください。

今回のアプリケーションでは以下のような技術を使用しています。

#### スタック

- OpenNext
- shadcn/ui
- tailwindcss
- drizzle-orm
- better-auth
- ai-sdk

#### Feature

以下の機能を試してみたい人はリポジトリを見てもらえれば実装方法がわかると思います。

UI上の工夫
- `gpt-4o-transcribe`を使用した音声入力とAIによる校正
- インタビューでのAIのレスポンスのストリーミング
- 記事生成のAIのレスポンスのストリーミング
- 記事OGの動的生成

裏側の工夫
- betterAuthで新規ユーザー作成後に他のDBにデータを登録する
- ストリームしたデータをサーバーサイドで保存する

日付で見ると結構多いなと思ったけど、実質の作業時間は4人日(8h * 4)くらいだったので4日ということにします。まるまる休みが4日あったら絶対できてたからね!(Vibe Codingはしてません)

git log --date=short --format="%ad" | sort -u

2025-07-28
2025-07-31
2025-08-03
2025-08-05
2025-08-09
2025-08-12
2025-08-13
2025-08-14

この記事を読むと何がわかるのか

この記事を読むと、「OpenNextを使用したNext.js on Cloudflare Workersの構成の開発でどんなことができるのか」ということがわかります。

結論から言うと、今まで通りのNext.jsでの開発に加え、Next.jsのサーバーサイドのコードでCloudflare Workersのリソースやコンテキストを使用した開発ができます。(next devでも使用できます)

これによって今回、Vercelでホスティングするときに比べて費用が抑えられたり、D1やKVを使用できるので、他のクラウドDBを使用するよりも費用が抑えられるというメリットがあります。

なんかここまで書いてみて、ドキュメントに書いてあるし普通にドキュメント見てる人なら誰でも知ってるなと思ったんですが、まあ具体的な使用例として見て貰えばと思います。

リポジトリに関しては公開してるので、必要であれば参照ください。

https://github.com/shinaps/thisisooo

あらかじめ

言っておくと、これはCloudflare WorkersのPaid Planが必要になると思います。めちゃくちゃシンプルな構成なら不要ですが、そもそもOpenNextがでかいので、自分はPaid Planが必要になりました。

本編

セットアップ

セットアップに関してはCloudflareのドキュメントを参考にしました。

https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/

ここに書いてある通り、以下のコマンドだけで最初の土台が出来上がります。

pnpm create cloudflare@latest {appName} --framework=next

環境変数について

ここからは、体系的にドキュメントにまとめられていなかったり、探すのが難しいと思ったものについてリンク貼ったり、サンプルを示したりして行きます。丸ごとAIに読ませて開発したらいい感じになると思います。

https://opennext.js.org/cloudflare/howtos/env-vars

Cloudflare Workersでローカルの環境変数を使用する場合は.dev.varsというファイルを使用するのですが、OpenNextでもこれを使用できるようになっています。ですが、next devなどの実行時に読み込むことができないので、環境変数自体は.env.env.developmentなどに記載します。

next devの実行時に.env.developmentを読み込みたい場合は以下のように.dev.varsNEXTJS_ENVを指定します。.dev.varsに書くのはこれだけです。

# .dev.vars
NEXTJS_ENV=development

そして、以下のような環境変数は.env.developmentに記載します。

import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'

export const env = createEnv({
  server: {
    BETTER_AUTH_SECRET: z.string(),
    BETTER_AUTH_URL: z.url(),
    TURSO_DATABASE_URL: z.url(),
    TURSO_AUTH_TOKEN: z.string(),
    GOOGLE_CLIENT_ID: z.string(),
    GOOGLE_CLIENT_SECRET: z.string(),
    OPENAI_API_KEY: z.string(),
    OPENAI_ADMIN_API_KEY: z.string(),
    OPENAI_PROJECT_ID: z.string(),
  },
  client: {
    NEXT_PUBLIC_APP_URL: z.url(),
  },
  runtimeEnv: {
    BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,
    BETTER_AUTH_URL: process.env.BETTER_AUTH_URL,
    TURSO_DATABASE_URL: process.env.TURSO_DATABASE_URL,
    TURSO_AUTH_TOKEN: process.env.TURSO_AUTH_TOKEN,
    NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
    GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
    GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
    OPENAI_API_KEY: process.env.OPENAI_API_KEY,
    OPENAI_ADMIN_API_KEY: process.env.OPENAI_ADMIN_API_KEY,
    OPENAI_PROJECT_ID: process.env.OPENAI_PROJECT_ID,
  },
})

next dev時のBindingsについて

next dev時のBindingsではwrangler.jsoncenvdevelopmentとかに設定してあっても読み込まれないので、トップレベルでの指定が必要になります。ローカルではidとかは適当でも動きます。

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "thisisooo",
  "main": ".open-next/worker.js",
  "compatibility_date": "2025-03-01",
  "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
  "assets": {
    "binding": "ASSETS",
    "directory": ".open-next/assets"
  },
  "observability": {
    "enabled": true
  },
  "workers_dev": false,
  
  "kv_namespaces": [
    {
      "binding": "KV",
      "id": "next-dev-kv"
    }
  ],
  "d1_databases": [
    {
      "binding": "D1",
      "database_name": "d1",
      "database_id": "next-dev-d1",
      "migrations_dir": "drizzle/d1"
    }
  ],
}

preview時には以下のようにしてenvを指定すればwrangler.jsoncの対象のenvのBindingsが使用できるようになります。

opennextjs-cloudflare build && opennextjs-cloudflare preview --env development

Preview時のエラーについて

https://opennext.js.org/cloudflare/howtos/workerd

Previewで動かそうとしたときに、バンドル時にエラーが発生することがありますが、こちらに書いてあるように、対象のライブラリをnext.config.tsserverExternalPackagesに追加することでエラーを解消できるようになります。


const nextConfig: NextConfig = {
  serverExternalPackages: [
    '@libsql/client',
    '@libsql/isomorphic-ws',
  ],
}

D1の使用

https://opennext.js.org/cloudflare/howtos/db#d1-example

getCloudflareContextを使用することで、サーバーサイドではどこでもCloudflare Workersのリソースにアクセスすることができるようになります。

上記ページにあるように、getDbという関数を作成してDBにアクセスするようにすると良いです。

import { getCloudflareContext } from "@opennextjs/cloudflare";
import { drizzle } from "drizzle-orm/d1";
import { cache } from "react";
import * as schema from "./schema/d1";
 
export const getDb = cache(() => {
  const { env } = getCloudflareContext();
  return drizzle(env.MY_D1, { schema });
});
 

export const getDbAsync = cache(async () => {
  const { env } = await getCloudflareContext({ async: true });
  return drizzle(env.MY_D1, { schema });
});

リクエストのキャッシュ

https://opennext.js.org/cloudflare/caching#caching

https://opennext.js.org/cloudflare/caching#queue

現在はISRとか使用せずに単純に重いfetchをrevalidate: 3600でキャッシュしているだけなので、以下のようにwrangler.jsoncに追加するのと、

  "services": [
    {
      "binding": "WORKER_SELF_REFERENCE",
      "service": "thisisooo"
    }
  ],
  "r2_buckets": [
    {
      "binding": "NEXT_INC_CACHE_R2_BUCKET",
      "bucket_name": "thisisooo-next-incremental-cache-prod"
    }
  ],

以下のようにopen-next.config.tsに記載するだけでキャッシュが使用できるようになります。

import { defineCloudflareConfig } from '@opennextjs/cloudflare'
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache'

export default defineCloudflareConfig({
  incrementalCache: r2IncrementalCache,
})

また、withRegionalCacheを使用することでfetch時のR2キャッシュへのアクセスを減らすことができます。

import { defineCloudflareConfig } from '@opennextjs/cloudflare'
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache'
import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache'

export default defineCloudflareConfig({
  incrementalCache: withRegionalCache(r2IncrementalCache, {
    mode: 'long-lived',
    shouldLazilyUpdateOnCacheHit: true,
  }),
})

その他

https://opennext.js.org/cloudflare/bindings#other-cloudflare-apis-cf-ctx

に記載されている通り、waitUntilなどもNext.jsのサーバーサイドで使用することができます。

終わりに

今回はNext.js on Cloudflare Workersの構成でフルスタックのアプリケーションを開発してみました。

Vercelでは手軽さと引き換えに、ユーザーが増えた時の費用面の負担があったのですが、今回Cloudflareで問題なく動かせることがわかったので、今後もこの構成で開発することが多くなりそうです。

Server Actionsを使用した開発は一人でどちらもできる人であれば高速にフルスタックアプリケーションを開発できるので、早く安くそして品質高くできる構成としてこれはかなり良いと思いました。



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -