😘

触ってみてわかったNext.jsと比べた時のRemixの特徴

2021/11/23に公開
2

本日11月23日Remixが正式にリリースされたので早速触ってみました。

https://remix.run/

Remixとは react-routerというライブラリーの開発元が作ったReact製のフレームワークで、Next.jsと同じようにファイルベースでルーティングできます。
そうなると、Next.jsと一体何が違うのかと感じている人も多いと思うので、
この記事ではNext.jsと比べた時のRemixの考え方の違いをまとめたいと思います。

SSGやISRはサポートしていない

Next.jsにはSSGやISRなどあらかじめデータをビルドしておいてその結果をリクエスト時に返すといった機能がありますが、Remixにはありませんでした。
その代わりRemixでは以下のようにページコンポーネントごとにheadersをexportするだけでHTTP headerをコントロールできる仕組みがあるので、そこでCache-Control HTTPヘッダーを使ったりして調整することができます。

export const headers: HeadersFunction = ({ loaderHeaders }) => {
  return {
    "Cache-Control": "max-age=3600, stale-while-revalidate=360"
  },
}

以下のドキュメントにあるようにRemixではSSGやISRを使ったキャッシュ戦略よりも、Cloudflare WorkersなどのEdgeで実行することや、Cache Controlなどによってパフォーマンス向上を図ることを推奨しているようですね。

https://remix.run/docs/en/v1/guides/performance

環境にロックインされない

もちろんNext.jsでは、GCPやServerlessなど他の環境にデプロイすることも当然可能なのですが、VercelにデプロイすることでISRやnext/imageなどパフォーマンスの最適化の上でいくつか恩恵が受けられます。
Remixの場合、Expressと連携することができたりするほか、VercelはもちろんAWS LambdaFly.ioCloudflare Workersなど他の環境で動かせるアダプターが用意されているので、特定の環境にロックインされないといった点でいいですね!

Routesごとにmetaタグやlinkタグなどが設定できる

RoutesとはNext.jsでいうところのページコンポーネントにあたるのですが、ページごとにmetaタグやlinkタグを以下のようにexportするだけで簡単に定義できます。

export const meta: MetaFunction = () => {
  return {
    title: "About Remix"
  };
};

export const links: LinksFunction = () => {
  return [{ rel: "stylesheet", href: stylesUrl }];
};

Next.jsのライブラリでいうところのnext-seo的なことが楽にできていいですね!
特定のページでのみCSSを読み込むといった時にも利用できます。

Routesごとのエラーハンドリング

RemixではRoutesごとにエラー時の表示をErrorBoundaryCatchBoundaryコンポーネントをexportすることで設定することができます。
ErrorBoundaryはクライアントサイドのコンポーネントで意図しないエラーが発生した場合に表示でき、
CatchBoundaryはページを表示する際やPOST時にサーバーサイドからエラー系のステータスコードが返ってきた際に表示できます。

export function ErrorBoundary({ error }) {
  return (
    <div>
      <h1>{error.message}</h1>
    </div>
  );
}

CatchBoundaryでは、以下のようにエラーの内容をuseCatchを使って取り出すこともできます。

export function CatchBoundary() {
  const caught = useCatch();

  return (
    <div>
      <h1>{caught.status} {caught.statusText}</h1>
    </div>
  );
}

Next.jsよりは各ページごとのエラー時のBoundary配置が簡単にできますね。

ページごとの部分的なルーティング

Nested Routesという機能ですが、個人的にこれがNext.jsにはない機能で一番面白いと感じました。OutletというRemixが提供しているコンポーネントを使ってネストされたURLに対して一部分にのみ適応するコンポーネントを設定することができます。
例えば、以下のようなコンポーネントがroutes/users.tsxに定義されていたとします。

import { Outlet } from "remix";

export default function Index() {
  return (
    <Layout>
      <Main>
        <Outlet />
      </Main>
      <Sub />
    </div>
  );
}

この場合、<Outlet />に対するコンポーネントをroutes/users/$id.tsxなどに設定することができます。
各Nested Routesにも先程のErrorBoundaryが設定できるので、routes/users/$id.tsxでエラーが起こりコンポーネントが死んだとしても、<Outlet />の部分のみErrorBoundaryに置き換わります。
つまり、Reactでよくあるような一部書き損じたためにいきなりページ全体が死ぬといったことがなくなります。
堅牢なWebアプリケーションを作れるといった点では確かに良さそうです!

POST処理も同じRoute内に書ける

ページコンポーネントが置かれているファイルにPOSTされた時の処理も書いておけるので見通しがいいですね。
Next.jsでいう、pages/api/以下に書いてた処理をページコンポーネントに持っていけるイメージです。

remixからFormコンポーネントとuseActionDataというhooksを利用することでこれが実現できます。

todo.tsx
import { Form, useActionData, json } from "remix";

export async function action({ request }) {
  const body = await request.formData();
  return json({ result: 'OK' })
}

export default function Todo () {
  // ここでAPI(action)から帰ってきたデータを受け取れる
  const data = useActionData()
  return (
    <Form method="post">
      {/* ここにフォームの内容 */}
    </Form>
  )
}

まとめ

触ってみた所感としてNext.jsと比べた時にSSGやISRができないことは少し不便だと思いましたが、以下のようにページごとにできることが多く、キャッシュやエラー時のハンドリングなどいろいろな戦略を立てられると感じました。

  • ページごとにlinkやmetaタグを設定できる
  • ページごとにheader情報を書き換えられる
  • ページごとにPOST処理もかける
  • ページごとにErrorBoundaryを設定できる
  • Nested Routesを利用してURLごとに一部分だけコンポーネントを書き換えることもできる

ただし、以下のようなNext.jsでサポートされている機能でRemixでは使えないいくつかの機能はあるのでどちらを使うかは状況によりけりといった感じです。

  • SSGやISRは使えない
  • CSS Modulesなどの便利なスタイリング機能はない
  • Next.jsのpages/api/**のようなAPI Routesがない

パフォーマンスの側面で言えば、RemixではNext.jsで提供されているようなトリッキーな機能をあえて提供せず、開発者の各々がページごとに必要に応じてlink rel="preload"Cache-Controlを設定するなど、Webの標準的な技術を利用してパフォーマンスの最適化を図ることを促したい意図がRemixにあるように感じました。

Webの知識が十分ある方にはRemixで色々とチューニングできそうですが、パフォーマンス改善の知見が浅い場合にはお作法に則れば最適化をしてくれるので、Next.jsがお勧めですね。

まだ十分に調べられてないところがあるので何かあれば追記します。また、間違っているところなどあれば温かいご指摘などいただけると幸いです。

Discussion

プログラミングをするパンダプログラミングをするパンダ

next/linkのようなページを事前フェッチングする機能はない

Link コンポーネントの props に応じて prefetch の設定が可能ですね。

デフォルトでは prefetch は off とのことです。

Link can automatically prefetch all the resources the next page needs: JavaScript modules, stylesheets, and data.

<Link /> // defaults to "none"
<Link prefetch="none" />
<Link prefetch="intent" />
<Link prefetch="render" />

https://remix.run/docs/en/v1/api/remix#link

Godai HoriGodai Hori

なるほど!あったんですね。
わざわざコメントいただきありがとうございますmm