話題の記事

ReactベースのあたらしいフレームワークRemixをためしてみた

OSSとしてリリースされたばかりのReactベースのフルスタックWebフレームワークであるRemixをためしてみました。
2021.11.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、CX事業本部MAD事業部の森茂です。

re:Inventを前にAWSの情報も気になるところですが、フロントエンド界隈もReact Conf 2021を前にReact v18 betaをはじめ、Next.js v12やReact Router v6、新しいRoutingライブラリReact Locationのリリースなどなど注目のリリースラッシュが続いているようです。そんな中Reactをベースにした新しいフレームワークであるRemixが本日(2021/11/23日本時間)リリースされました。

Remixとは

RemixはReactRouterの作者でもあるMichael Jackson氏(@mjackson)とRyan Florence氏(@ryanflorence)がつくったReactをベースとした新しいフルスタックなフレームワークです。昨年より「supporter preview」としてライセンス購入者向けにベータ版を公開しながら開発を進めていましたが、先日$300万の資金提供を受け「supporter preview」を終了しOSS化することを発表。日本時間の2021年11月23日6:00にYouTubeライブにてOSSとしてv1.0がリリースが発表されました。

また、つい先日The Testing Torophyの考案者でReact Tesing libraryの作者でもあるKent C. Dodds氏(@kentcdodds )がRemixプロジェクトに参画することが発表されされ、さらに注目を浴びています。

RemixはReactをベースとした他のすでに人気のあるフレームワークのNext.jsやGatsbyとは路線が異なり、SSGなど予めデータを用意して高速化を測る手法はとらず、サーバーサイドレンダリングやブラウザのFetchAPIを活用してデータをやり取りするといった手法をとっているようです。先日リリースされたReact Router v6の機能でもあるNested routesを使いコンポーネント間の関連する部分のみを更新するなど、キャッシュ戦略を駆使したフレームワークになっています。(一部個人的な推測を含みます)

またデプロイ先としてはベンダーロックイン0を謳っており、さまざまな環境で動作することを強みとしているようです。ただ独自のアプリケーション・サーバーが動作することが必要なため(Node.js v14以上)デプロイ環境についてはどこでも動かせるというわけにはいかないかもしれません。最近個人的にも気になっているCloudflare WorkersのKV StorageやDurable Objectsを使って、アプリをエッジにデプロイすることもできるようです。$ arc deployでLambda + API Gatewayにもデプロイ可能ですがパッケージサイズが非常に大きいため事前に準備が必要です。

どんな機能をもっているかの概要はフレームワークのトップページを見ていくことで体験することができるので、まずはRemixのサイトを見てスクロールしてみてください。なぜかサイト内にはわかる人にはわかる↑↑↓↓←→←→BAといった記載もあります:)

Remixのチュートリアルをやってみる

実際にどのようなものが作れるのか、早速チュートリアルとして用意されているQuickstartをやってみました。 以下チュートリアルを実際に進めながら気づいたこと感じたことを記載している形となっていますので、実際の画面を体験もしくは見ながら読んでいただけると幸いです。また、リリース直後の情報を読み進めながらのため私自身の理解や解釈が誤っている場合もございますのであらかじめご了承ください。

インストール

早速インストールしていきます。

$ npx create-remix@latest

Install

途中デプロイ先を選ぶ箇所があるのですが、今回はRemix App Serverを選択しています。他の選択肢ではどのようなソースになるかも気になるところです。

select-deploy-target

ディレクトリ構成

my-remix-app
├── .cache/
├── README.md
├── app/
├── build/
├── node_modules
├── package-lock.json
├── package.json
├── public/
├── remix.config.js
├── remix.env.d.ts
└── tsconfig.json

起動

$ npm run dev

http://localhost:3000 でサンプルアプリケーションが立ち上がありました。

起動画面

Your First Route

まずはapp/root.tsxファイルの編集から。ソースを見る感じCRAで作成したReactアプリケーションでのindex.tsxApp.tsxを足したようなものになるのでしょうか。metaタグやcss、ErrorBoundaryなどをこのファイルで設置しているようです。またサンプルアプリではレイアウトコンポーネントなどもここで用意しているみたいです。

ReactRouter v6の新機能Outletも使われてますね:)

app/root.tsx

/**
 * The root module's default export is a component that renders the current
 * route via the `<Outlet />` component. Think of this as the global layout
 * component for your app.
 */
export default function App() {
  return (
    <Document>
      <Layout>
        <Outlet />
      </Layout>
    </Document>
  );
}

404_Not_Found

リンクを設置してブラウザでアクセスするとちゃんと404のステータスコードが返ってきました。SPAだとページがなくても200を返すことになってしまうので地味にこれはすごい。ステータスコードにあわせたエラーページもuseCatchというhooksを利用して実現しているようです。

root.tsx

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

  let message;
  switch (caught.status) {
    case 401:
      message = (
        <p>
          Oops! Looks like you tried to visit a page that you do not have access
          to.
        </p>
      );
      break;
    case 404:
      message = (
        <p>Oops! Looks like you tried to visit a page that does not exist.</p>
      );
      break;

    default:
      throw new Error(caught.data || caught.statusText);
  }

  return (
    <Document title={`${caught.status} ${caught.statusText}`}>
      <Layout>
        <h1>
          {caught.status}: {caught.statusText}
        </h1>
        {message}
      </Layout>
    </Document>
  );
}

app/route/posts/index.tsxファイルを追加。routeディレクトリに配置したファイル名がそのままURLとして利用できる模様。このあたりはNext.jsのpagesディレクトリと同じような扱いになりそうです。

Loading Data

さきほど作成した空のコンポーネントにデータを投入していきます。RemixではuseLoaderDataというhooksを利用してコンポーネントにデータを入れることができるみたいです。Next.jsでいうgetServerSidePropsみたいな感じでしょうか。

ssr

loaderとして用意したデータが事前にレンダリングされているのも確認できました。

A little refactoring

チュートリアルの通りにリファクタリングしていきます。すでにここでgetPosts()に集約することでデータソースとしては何でもいけそうな雰囲気を醸し出していますね:) 一箇所ソースの修正、app/post.tsに移したtype Postexport type Postとしておく必要ありです。

Pulling from a data source

ハードコーディングしたデータから出力していた部分をファイルシステムからMarkdownを読み込む形に変更していくようです。このあたりをデータベースとやり取りする形に書き換えればもう立派なアプリケーションとして活用できそうですね。若干Next.jsのチュートリアルと同じような雰囲気になってきました:)

Dynamic Route Params

動的なルートもroutesディレクトリ下に$変数名.tsxと配置することでparamsから取得できるようです。

$slug.tsx

import { useLoaderData } from "remix";
import type { LoaderFunction } from "remix";

export let loader: LoaderFunction = async ({ params }) => {
  return params.slug;
};

export default function PostSlug() {
  let slug = useLoaderData();
  return (
    <div>
      <h1>Some Post: {slug}</h1>
    </div>
  );
}

Creating Blog Posts

新規投稿用のadminコンポーネントを作成していきます。cssの読み込ませ方がちょっと独特ですね。

app/routes/admin.tsx

import { Link, useLoaderData } from "remix";
import { getPosts } from "~/post";
import type { Post } from "~/post";
import adminStyles from "~/styles/admin.css";

export let links = () => {
  return [{ rel: "stylesheet", href: adminStyles }];
};

Index Routes

引き続きadminコンポーネントを作成していきます。Outletの使い方も兼ねたチュートリアルになっているみたいです。

Actions

ここからRemixのFormコンポーネント、useActionDatauseTransitionと立て続けに機能の実装と紹介になっています。

app/routes/admin/new.tsx

import { Form, redirect } from "remix";
import type { ActionFunction } from "remix";
import { createPost } from "~/post";

export let action: ActionFunction = async ({ request }) => {
  let formData = await request.formData();

  let title = formData.get("title");
  let slug = formData.get("slug");
  let markdown = formData.get("markdown");

  await createPost({ title, slug, markdown });

  return redirect("/admin");
};

これだけの記述でformのデータをやり取りできるとは。。。

またuseTransitionを使うとPOSTやGETなどメソッドに合わせた状態なども取得できるようです。

app/routes/admin/new.tsx

import {
  useTransition,
  useActionData,
  Form,
  redirect
} from "remix";

// ...

export default function NewPost() {
  let errors = useActionData();
  let transition = useTransition();

  return (
    <Form method="post">
      {/* ... */}

      <p>
        <button type="submit">
          {transition.submission
            ? "Creating..."
            : "Create Post"}
        </button>
      </p>
    </Form>
  );
}

Homework

つづいて投稿の編集画面もつくってみようというところでチュートリアルは終わります。

さいごに

今朝リリースされたばかり(執筆時時点)のRemixのチュートリアルを駆け足で体験してみました。QuckstartではRemixの触り部分だけでしたが、ドキュメントやGitHubを見る限り面白そうな機能や実装がたくさんあるようなのでこれからさらに深堀りしていきたいですし、SSGには頼らずにサーバーサイドで快適な速度を実現していこうという試みと、それをベンダーロックインなくフレームワーク側で用意したコンポーネントやhooksで簡単にハンドリングできるようにしている部分など、最近のはやりとはちょっと違う視点のロジックが隠されていそうでわくわくしています。

ちなみに、Quickstartとは別にJokes AppというPrismaでsqliteを利用した本格的なチュートリアルも紹介されているので、そちらもこれから試してみます:)

なお、Jokes AppはCode Sandboxでも早速体験できるようになったようです。