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
/hoge
app/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
loader
during the initial server-rendered document request -
in an
action
during the initial server-rendered document request -
in a
loader
during a client-side transition in the browser (Remix serializes the error and sends it over the network to the browser) -
in an
action
during 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.",
},
];
};