Next.jsのServerComponentからRustのasync fnを使用する
2023-09-30
ウェブアプリケーションを実装する際、個人的にも組織としてもbackendをRust
で、frontend をReact/TypeScript
で実装しその間はGraphQL
またはgrpc
となることが多いのだけれども、Next.js
のServerComponent
からnapi-rs
を使用してRust
の関数を直接読んでやるとかなり楽できるのではないかと思いそういうことができそうか試してみた。
目次
INFO
この記事はZennに記載した記事
のCloneとなっている。
成果物
とりあえずnpm run dev
で動作は確認できているがbuild
する場合はprebuilt
されたbinary
へのパスは調整する必要があるかもしれない。
セットアップ
まずはすべてデフォルト設定で OK なのでRSC
だけ有効にしてNext.js
のプロジェクトを立ち上げる。 その後以下に従いnapi-rs
のセットアップを行う。
napi
の設定はpackage.json
に書くようで、今回はひとまず手元の M1 mackbook で動作させることをゴールとしているのでtriples
にaarch64-apple-darwin
を指定している。
Rust 側の関数
今回はサンプルとしてsrc/lib.rs
に以下のようなasync fn
を用意した。
#[macro_use]
extern crate napi_derive;
#[napi]
pub async fn async_sum(a: i32, b: i32) -> i32 {
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
a + b
}
index.js の修正
この状態でnpm run build
するとプロジェクトのルートにindex.js
とnapi.darwin-arm64.node
が吐かれる。
が、そのままでは動かないで修正していく。詳細は省略するが以下のようにswitch
でplatform
とarch
を判別するコードが吐かれる。
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
...
require("xxxxx.node")
...
break
case 'arm':
...
require("xxxxx.node")
...
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
...
今回はaarch64-apple-darwin
だけ考慮するのと、require
を使用してしまうとwebpack
側のrequire
が使用されてしまうので以下のように__non_webpack_require__
を利用してbinary
を読み込むようindex.js
を修正する。
また、このindex.js
は.next/server/app
に配置されることを前提として*.node
のパスを指定する必要がありそう。今回はnpm run dev
で動けばよし。としているのでbuild
する場合はこの辺のパスに注意すること。
const node = __non_webpack_require__("../../../napi.darwin-arm64.node"); // use relative path from .next/server/app
export const { asyncSum } = node;
index.js の利用
上記で変更したindex.js
はsrc/app/page.tsx
から以下のように使用する。
import { asyncSum } from "../../";
export default async function Home() {
const result = await asyncSum(1, 2);
return <main>Result: {result}</main>;
}
以下のように表示されたら成功だ。
Result: 3
まとめ
たとえばデスクトップ PC だけを想定したアプリや管理画面などはこの方法で実装すると楽ではないかと考え試してみた。実際にproduction
で動かすのであればまだ考慮することはありそうだが、ひとまずasync
関数をNode
から使用できることが確認できた。今後の選択肢に加えてもう少し検証してみたい。
以上。