Skip to content
On this page

Next.jsのServerComponentからRustのasync fnを使用する

2023-09-30

ウェブアプリケーションを実装する際、個人的にも組織としてもbackendをRustで、frontend をReact/TypeScriptで実装しその間はGraphQLまたはgrpcとなることが多いのだけれども、Next.jsServerComponentからnapi-rsを使用してRustの関数を直接読んでやるとかなり楽できるのではないかと思いそういうことができそうか試してみた。

目次

INFO

この記事はZennに記載した記事のCloneとなっている。

成果物

とりあえずnpm run devで動作は確認できているがbuildする場合はprebuiltされたbinaryへのパスは調整する必要があるかもしれない。

bokuweb/rsc-napi-sandbox
rsc-napi-sandbox
Rust

セットアップ

まずはすべてデフォルト設定で OK なのでRSCだけ有効にしてNext.jsのプロジェクトを立ち上げる。 その後以下に従いnapi-rsのセットアップを行う。

Getting started – NAPI-RS

napiの設定はpackage.jsonに書くようで、今回はひとまず手元の M1 mackbook で動作させることをゴールとしているのでtriplesaarch64-apple-darwinを指定している。

Rust 側の関数

今回はサンプルとしてsrc/lib.rsに以下のようなasync fnを用意した。

rust
#[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.jsnapi.darwin-arm64.nodeが吐かれる。
が、そのままでは動かないで修正していく。詳細は省略するが以下のようにswitchplatformarchを判別するコードが吐かれる。

javascript
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する場合はこの辺のパスに注意すること。

javascript
const node = __non_webpack_require__("../../../napi.darwin-arm64.node"); // use relative path from .next/server/app

export const { asyncSum } = node;

index.js の利用

上記で変更したindex.jssrc/app/page.tsxから以下のように使用する。

typescript
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から使用できることが確認できた。今後の選択肢に加えてもう少し検証してみたい。

以上。