Remixとは
Remixは、Reactをベースとしたフルスタックフレームワークです。
フルスタックフレームワークを使っているというよりは、Reactで開発しながら、サーバーサイドの処理も同時に書けるのがRemixです。
Remix はこれらすべてのコンポーネントを事前にレンダリング(SSR)するため、サーバー上で完成したページをエンド ユーザーに提供します。
Remix vs NextJS
| Remix | NextJS | |
|---|---|---|
| レンダリング | 常にSSR | SSRはオプション |
| サイト生成 | 静的サイト生成なし(ビルド時のプリレンダリングなし) | 静的サイト生成(ビルド時)がサポートされている |
| ホスト要件 | 常にサーバーサイドコード実行をサポートするホストが必要 | 静的ホスティングとサーバーサイドコード実行のデプロイオプションがある |
Remixのコアコンセプト
remixでのルーティング
app配下のデフォルトの構造
app
┣ routes
┃ ┣ _index.tsx
┃ ┗ hoge.tsx
┣ entry.client.tsx
┣ entry.server.tsx
┗ root.tsx
-
routes配下に作成したファイルはそのままURL にマッピングされる。URL Matched Routes /app/routes/_index.tsx/hogeapp/routes/hoge.tsx -
ページ内の遷移にはLinkを用いる、Linkはclient side routingを採用しているのでページ全体のリクエストは行われない。
import { Link } from "@remix-run/react"; export default function Sample() { return ( <> <h1>Sample Page</h1> <Link to="/hoge">Go to hoge Page</Link> </> ); }📖client side routingとは
ページ間のページ遷移がブラウザ上(クライアントサイド)で行われることを指す。
この方法では、新しいページへのリンクがクリックされると、ブラウザは新たにページ全体をサーバから取得するのではなく、必要なデータだけを取得(通常はAPI経由)し、それを用いて新しいページをレンダリングする。参考
Styleの設定
-
Remix でPlaneなCSSを使用する場合
-
Styleの設定にはlinksを利用します。詳細は下記を参照。
📖Remixにおける
links機能の使用について-
root.tsxでのLinksの使用:root.tsxはアプリケーション全体の基盤となるコンポーネントです。ここで定義される<Links />は、全てのページで共通して適用されるCSSなどのリソースを含みます。これにより、アプリケーション全体にわたる共通のスタイルや設定を行うことができます。 -
routesディレクトリでのlinksの使用:routesディレクトリ内の各ファイルでは、その特定のルートに関連するリソースを定義することができます。ここでLinksFunctionを使ってリンクを定義すると、Remixはそのルートがアクティブになったときに自動的にそれらのリンクを<head>に追加します。つまり、各ルートで定義されたスタイルやリソースは、そのルートがロードされるときにのみ適用され、ルートごとに異なるスタイリングやリソースを持たせることができます。
-
-
ComponentへのStyleの適用に関しては下記参照
Componentで設定したstyleは結局はページで全て適用されるので、スコープはComponentに閉じないので、classNameの重複に注意が必要です。
-
Styleの設定にはlinksを利用します。詳細は下記を参照。
- Remix でCSS moduleを利用する場合
データの送受信
-
送信
データの送信には
actionを使います。データの変更やその他のアクションを処理するサーバー専用の機能です。
GETルート以外のmethod(DELETE、PATCH、POST)でリクエストが行われた場合に、actionはloaderの前に呼び出されます。フォーム要素もremix独自のFormを用いることで、client side routingを採用しているのでページ全体のリクエストは行われない。
-
受信
データの受信には
loaderを使います。データの受信を行うサーバー専用の機能です。
GETでリクエストが呼び出された時に実行されます。コンポーネント内で受信したデータを利用する場合には、useLoaderData()を利用してデータを受け取ります。export default function NotesPage() { const notes: Note[] = useLoaderData(); return ( <main> <NewNote /> <NoteList notes={notes} /> </main> ); } export async function loader() { const notes = await getStoredNotes(); return json(notes); }
バリデーション
Validationを行う先には、Action内でデータの検証を行います。
データにエラーがあり、独自のエラーメッセージを返してブラウザで表示させたい場合には、Actionはサーバーサイドで実行されるため、Windowメソッドなどを呼び出すことはできません。
エラーが発生した場合にはAction内でObjectをリターンします。
useActionDataを使いブラウザにメッセージ出力用のデータなどを取得します。
連打処理を行う際には、useNavigationを使用します。
useNavigationでは、保留中のページ ナビゲーションに関する情報を提供します。
isSubmittingなどの送信中の状態も確認することができ、送信中の間にはbutton を無効にするなどして連打処理を実現できます。
具体例
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const noteData: Note = {
id: new Date().toISOString(),
title: formData.get("title") as string,
content: formData.get("content") as string,
};
if (noteData.title.trim().length < 5) {
return { message: "タイトルは5文字以上で入力してください" };
}
const existingNotes = await getStoredNotes();
const updatedNotes = existingNotes.concat(noteData);
await storeNotes(updatedNotes);
return redirect("/notes");
}
import type { action } from "~/routes/notes";
export function NewNote() {
const data = useActionData<typeof action>();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post" id="note-form">
{data?.message && <p>{data.message}</p>}
<p>
<label htmlFor="title">Title</label>
<input type="text" id="title" name="title" required />
</p>
<p>
<label htmlFor="content">content</label>
<textarea id="content" name="content" rows={5} required />
</p>
<div className="form-actions">
<button disabled={isSubmitting}>
{isSubmitting ? "Adding..." : "Add Note"}
</button>
</div>
</Form>
);
}
エラーハンドリング
共通的なエラーの処理を行う場合は、ErrorBoundaryを利用します。
-
Remix は、コード内のほとんどのエラーをサーバー上またはブラウザ上で自動的に検出し、エラーが発生した場所に最も近い
ErrorBoundaryをレンダリングします。 -
useRouteErrorからはaction、loader、またはレンダリング中にスローされたエラーにアクセスすることができます。具体例- rendering in the browser
- rendering on the server
-
in a
loaderduring the initial server-rendered document request -
in an
actionduring the initial server-rendered document request -
in a
loaderduring a client-side transition in the browser (Remix serializes the error and sends it over the network to the browser) -
in an
actionduring a client-side transition in the browser
-
カスタムエラーを表示させる場合には、エラー発生時にthrowをして特定のエラーを返します。そして
ErrorBoundary内でisRouteErrorResponseを使い、そのエラーを表示させます。//エラーをthrowする export async function loader() { const notes = await getStoredNotes(); if (!notes || notes.length === 0) { throw json( { message: "ノートが見つかりませんでした。" }, { status: 404, statusText: "Not Found" } ); } return notes; }import { useRouteError, isRouteErrorResponse, } from "@remix-run/react"; export function ErrorBoundary() { const error = useRouteError(); // when true, this is what used to go to `CatchBoundary` if (isRouteErrorResponse(error)) { return ( <div> <h1>Oops</h1> <p>Status: {error.status}</p> <p>{error.data.message}</p> </div> ); } // Don't forget to typecheck with your own logic. // Any value can be thrown, not just errors! let errorMessage = "Unknown error"; if (isDefinitelyAnError(error)) { errorMessage = error.message; } return ( <div> <h1>Uh oh ...</h1> <p>Something went wrong.</p> <pre>{errorMessage}</pre> </div> ); }
Routes File Naming
Routes配下のファイルの命名規則についてはこちらを参照
-
URL内の
/はroutes配下では.で表現します。 -
動的なURLは
$で表現します。 -
.区切りでネストしたルートを作成します。.の前のファイル名が他のルートのファイル名にマッチする場合、そのルートは自動的にマッチする親の子ルートになります。 -
URL をネストしたいが、自動レイアウトのネストは望まない場合があります。親セグメントの末尾に
_を付けてネストをオプトアウトできます。 -
セグメントの末尾に
_を付けると、ファイル名を覆い、URL からファイル名を隠すブランケットとなります。 -
ルートセグメントを
()で囲むと、セグメントがオプションになります。e.g.($lang) -
$は単一のパス セグメント (URL 内の 2 つのセグメントの間にあるもの) と一致しますが、Splat route(e.g.files.$.tsx)はスラッシュを含む URL の残りの部分と一致します。 -
Remix がこれらのルート規則に使用する特殊文字の 1 つを実際に URL の一部にしたい場合は、
[]文字を使用して規則をエスケープできます。
Metaデータの設定
特定のルートでmeta関数を設定することで、サイトのmeta情報を設定できます。
親ルートと小ルートが競合する場合は、より下位の小ルートの設定が優先されます。
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{
title: data ? data.title : "Loading...",
description: "Manage your notes with ease.",
},
];
};