はじめに
2017年9月、CloudflareはCloudflare Workersを発表し、JavaScriptを彼らのエッジネットワーク上で実行する機能を提供しました。これにより、コードは世界中の100以上のロケーションにあるエッジネットワーク全体に約30秒でデプロイされます。このテクノロジーにより、ユーザーが世界のどこにいても(〜50msの遅延で)、アプリケーションをユーザーの近くで書くことに集中できます。
WorkerのランタイムはNode.jsやブラウザと同じではなく、Google Chromeが開発したJavaScriptエンジンであるV8を使用してコードを実行します。これまで、彼らのプラットフォームで実行できたのは、パフォーマンスを向上させたり、リクエストヘッダーに基づいてロジックを追加したりするために、サーバーに到達する前にエッジで実行される小さなスクリプトでした。
2020年11月、Nuxt 3の開発中に、私たちはNuxtをエッジランタイム / V8アイソレートで本番稼働させるという賭けをしました。
これにより、Cloudflare Workersのようなプラットフォームを使用することで、サーバー、ロードバランサー、キャッシングに対処することなく、世界中から約50msでページをサーバーレンダリングする機能が解放されます。料金はリクエスト100万回あたり0.3ドルです。現在、Deno DeployなどのV8アイソレート上でアプリケーションを実行できる新しいプラットフォームが登場しています。
課題
Nuxtをワーカーで実行できるようにするために、Nuxtの一部を環境に依存しないように(Node.js、ブラウザ、V8で実行できるように)書き直す必要がありました。
私たちはサーバーから始め、unjs/h3を作成しました。これは、高性能とポータビリティのために構築された最小限のhttpフレームワークです。これはNuxt 2で使っていたConnectを置き換えますが、互換性があるため、Connect/Expressミドルウェアを使い続けることができます。ワーカーでは、受信するリクエストごとに本番環境でNuxtを起動し、リクエストを送信して応答を返します。
Nuxt 2では、本番環境でサーバーをメモリ内で起動するまでの時間(コールドスタートとも呼ばれます)は約300msでした。これは、リクエストを処理するためにサーバーとアプリケーションのすべての依存関係をロードする必要があったためです。
h3の開発において、私たちはサーバーにアタッチされた各ハンドラをコード分割し、リクエストされたときにのみ遅延ロードすることにしました。Nuxt 3を起動すると、h3と対応するハンドラのみがメモリにロードされます。リクエストが来ると、ルートに対応するハンドラがロードされ、実行されます。
このアプローチを採用することで、コールドスタートを約300msから約2msに削減しました。
Nuxtをエッジで実行するために、もう一つの課題がありました。それは本番環境のバンドルサイズです。これには、サーバー、Vueアプリ、Node.jsの依存関係がすべて含まれます。Cloudflare Workersには現在、ワーカーサイズに1MB(無料プラン)と5MB(月額5ドルプラン)の制限があります。
これを達成するために、私たちはunjs/nitroというサーバーエンジンを作成しました。nuxt buildコマンドを実行すると、プロジェクト全体をバンドルし、すべての依存関係を最終出力に含めます。これはRollupGlobalComponentsvercel/nftを使用して、node_modulesで使用されるコードのみをトレースし、不要なコードを削除します。基本的なNuxt 3アプリケーションの生成される出力の合計サイズは、gzipで約700kBです。
最後に、開発環境(Node.js)とCloudflare上での本番環境(Edge runtime)の間で同じ開発者体験を提供するために、私たちはunjs/unenvを作成しました。これは、既知の依存関係のモックやポリフィルを追加することで、JavaScriptコードをどこでも実行できるように(プラットフォームに依存しないように)変換するライブラリです。
Nuxtでは、お客様に最適なホスティングプロバイダーを自由に選択できるべきだと考えています。
そのため、Nuxtアプリケーションをエッジサイドレンダリングで
- NuxtHub
- Cloudflare Pages
- Deno Deploy
- Vercel Edge Functions(内部的にはCloudflare Workersを使用)や
- Netlify Edge FunctionsDeno Deploy
(内部的にはDenoを使用)にデプロイできます。また、静的ホスティングや従来のNode.jsサーバーレスおよびサーバーホストなど、他の多くのデプロイプロバイダーもサポートしています。
フルスタック機能の推進
Nuxtがエッジランタイムで動作するようになったので、Vueアプリケーションをレンダリングする以上のことができます。サーバーディレクトリのおかげで、APIルートの作成はTypeScriptファイル一つで済みます。
/api/helloルートを追加するには、server/api/hello.tsファイルを作成します。
export default defineEventHandler((event) => {
return {
hello: 'world'
}
})
これで、このAPIをページやコンポーネントでユニバーサルに呼び出すことができます。
<script setup>
const { data } = await useFetch('/api/hello')
</script>
<template>
<pre>{{ data }}</pre>
</template>
useFetchと$fetchを作成した際、注目すべき重要な点があります。それは、サーバーサイドレンダリング中にAPIルートを呼び出すと、リクエストをエミュレートし、関数コードを直接呼び出すことです。これにより、HTTPリクエストを回避し、ページのレンダリング時間を短縮します。
開発者体験の観点では、サーバーファイルを作成する際に、Vueアプリを再構築することなくNuxtサーバーが実行し続けることに気づくでしょう。これは、Nuxt 3がAPIおよびサーバールートの作成時にホットモジュールリプレイスメント(HMR)をサポートしているためです。
さらに、drizzle-ormのようなオブジェクトリレーショナルマッピング(ORM)を活用することで、開発者はD1, Turso, Neon, Planetscaleなどのエッジ&サーバーレスデータベースに接続できます。
私はAtidoneを作成しました。これは、認証とエッジで動作するデータベースを備えたフルスタックアプリケーションをデモンストレーションするためのオープンソースデモです。ソースコードはGitHubでMITライセンスの下、atinux/atidone.
結論
で利用可能です。私たちはエッジサイドレンダリングとその可能性に興奮しています。Nuxtのチームは、皆さんがこの上に何を構築するかを見るのが待ちきれません!
ぜひ私たちのDiscordサーバーに参加するか、Twitterで@nuxt_jsにメンションして、あなたの作品を共有してください。