Remixでv2に移行する
はじめに
今回の記事ではRemixをv1からv2に移行する方法をまとめていきます。
それでは早速見ていきましょう。
CHANGELOG
2023/10/08
- v2リリースに伴い、future系のフラグが必要なくなったので記事から削除
-
LoaderArgs
がLoaderFunctionArgs
に変更された為それについて記述 - Remix関係のライブラリについて記述
loader や actionの型が変更
前まで | v2から |
---|---|
LoaderArgs | LoaderFunctionArgs |
ActionArgs | ActionFunctionArgs |
v2 route
これは routes
フォルダに関する変更です。ファイルなどが多いと結構大変かもしれませんが、頑張ってやっていきましょう。
基本ルート
v1の頃 routes/index.ts
だったものが routes/_index.ts
になります。
リンク | 一致するルート |
---|---|
/ | _index.tsx |
フラットルートが実装されました
実装されたというより、今後はこれがメインに近いと思った方がいいと思います。実際にどんな感じかというと以下の例が分かりやすいです。
これが
routes
├── users
│ ├── index.tsx # users直下のページ(例としてはユーザー一覧など
│ └── profile.tsx # プロフィール
└── users.tsx # レイアウト ここに Outletを書く
こうなります
routes
├── users._index.tsx # users直下のページ(例としてはユーザー一覧など
├── users.profile.tsx # プロフィール `users.tsx` のレイアウトが適応されない!
└── users.tsx # レイアウト ここに Outletを書く
ディレクトリ内に route.tsx
や index.tsx
がある場合は、他のモジュールはルートファイルとして認識されなくなります。つまり、 routes
フォルダにページに関連するコンポーネントやデータベースへのアクセスなどを集めることが可能になりました。これに関しては後述するので、とりあえずそういうのがあるという風に覚えておいてください。
また、ここで大事なのは users.tsx
と users._index.tsx
の機能がごちゃ混ぜにならないことです。今回はコメントをファイルの横に書いているのでそちらを読んでいただければわかりやすいかなと思います。
よりv2の構造が分かりやすくなるようにするため、表にしてみましょう。
※ルートというのは今回ファイルのことを指します
URL | 一致するルート | レイアウト |
---|---|---|
/users | users._index.tsx | users.tsx |
/users/profile | users.profile.tsx | users.tsx |
URLはネストするがレイアウトはネストしないということが可能に
「おまえは何を言っているんだ」と思った人もいるかもしれませんが、今から説明するので落ち着いて聞いてください。
これはURLとしては /users/profile
のようにネストさせたいが、 profile
に users
のレイアウトを適応したくない場合に役に立つ機能です。
使用する場合は、親セグメントの末尾にアンダースコアをつけることで使用できます。
以下に実際に使用する場合のファイル名を記載します
routes
├── users._index.tsx # users直下のページ(例としてはユーザー一覧など
├── users_.profile.tsx # プロフィール `users.tsx` のレイアウトが適応されない!
└── users.tsx # レイアウト ここに Outletを書く
今回はレイアウトを適応しないルートのファイル名はusers_.profile.tsx としています。
ちなみにこの機能を使用してレイアウトを適応しなかった場合は root.tsx
のレイアウトが適応されます。
一応表にして記載すると以下のようになります。
URL | 一致するルート | レイアウト |
---|---|---|
/users | users._index.tsx | users.tsx |
/users/profile | users_.profile.tsx | root.tsx |
URLではネストしないが、ネストしたレイアウトが可能に
これもまた言葉では表しにくいのですが、まずは以下のディレクトリ構造を見てください。
app/
├── routes/
│ ├── _auth.login.tsx
│ ├── _auth.register.tsx
│ ├── _auth.tsx
│ ├── _index.tsx
│ ├── concerts.$city.tsx
│ └── concerts.tsx
└── root.tsx
これは以下のような表に直せます。
リンク | 一致するルート | レイアウト |
---|---|---|
/ |
_index.tsx |
root.tsx |
/login |
_auth.login.tsx |
_auth.tsx |
/register |
_auth.register.tsx |
_auth.tsx |
/concerts/salt-lake-city |
concerts.$city.tsx |
concerts.tsx |
今回の例で言うなら、 本来 auth/login
などになる物を /login
にした状態で _auth.tsx
のレイアウトを適応するというものです。これはどのように使用するかというと、対象としたいルートファイル名の先頭にアンダースコアをつけることで使用できます(今回なら_auth
)
フォルダを使用したルート
前からRemixを触ってた人はこう思ったのではないでしょうか、「え?フォルダ無いの?」と、安心してください!入ってますよ!
それではディレクトリ構造を先にご覧ください。
routes
├── users._index
│ └── route.tsx # users直下のページ(例としてはユーザー一覧など
├── users.profile
│ ├── get-profile-data.server.tsx # route.tsxで使う関数が入っている
│ └── route.tsx # プロフィールページ
└── users.tsx # レイアウト
さて、users.profile
で users/profile
が出来るのはもう先ほどの流れからなんとなくわかっている方も多いと思います。ここで、フラットルートの最初の方で触れた route.tsx
の機能を改めて説明します。
route.tsx
がある場合そのディレクトリ内のファイルはルートファイルとして認識されないと先ほど説明しましたが、これにより今回は get-profile-data.server.tsx
というファイルが新しく増えています。
これにはデータベースにアクセスしユーザーのプロフィールを取得するための関数などが入っています。v1の頃では loader
の中でやるか、 routes
フォルダの外で作成するしか無かったものが、関連性の高いものに纏められるようになったという認識で大丈夫です。
また、念のために説明しておくと route.tsx
はv1の頃にフォルダを使用した際に使用していた index.tsx
と同じものだと思えば大丈夫です。ただ、この場合はネストしたページだとしてもフォルダを分ける必要があります。それがv1とv2の大きな違いです。
Meta
metaの記述方式が変わります。v1ではオブジェクトにキーと値を入れる形でしたが、v2ではarrayにそれぞれ一つずつオブジェクトを入れる形になります
export const meta: MetaFunction = () => {
return {
title: "...",
description: "...",
"og:title": "...",
}
};
export const meta: MetaFunction = () => {
return [
{ title: "..." },
{ name: "description", content: "..." },
{ property: "og:title", content: "..." },
};
また、v1の記述のまま内部的にv2にしたいという場合は @remix-run/v1-meta
パッケージの metaV1
という関数が使用可能です。一応これで延命が可能ですが、個人的にはパッケージを追加する手間などを考慮すると素直に新しい記述にした方がいいと思います。
import { metaV1 } from "@remix-run/v1-meta";
export const meta: MetaFunction = (args) => {
return metaV1(args, {
title: "...",
description: "...",
"og:title": "...",
});
}
matches
どうもv1では nestされたrouteのオブジェクトはすべてマージされていたようで、v2では自身でマージを管理する必要があるようです。いまいちこれに関しては自分もよく分かってないので詳しくは公式ドキュメントのこちらを見てみてください。
export function meta({ matches }) {
const rootMeta = matches[0].meta;
const title = rootMeta.find((m) => m.title);
return [
title,
{ name: "description", content: "..." },
{ property: "og:title", content: "..." },
// you can now add SEO related <links>
{ tagName: "link", rel: "canonical", href: "..." },
// and <script type=ld+json>
{
"script:ld+json": {
"@context": "https://schema.org",
"@type": "Organization",
name: "Remix",
},
},
];
}
CatchBoundary
と ErrorBoundary
v1ではスローされた例外は最も近い物をレンダリングし、他の処理されなかった例外は ErrorBoundary
等でレンダリングされていましたが、v2ではそれらが無くなり、ひとまとめになっています。更に、エラーは props
で渡されなくなり、 useRouteError
hook で取得できるようになりました。
import { useCatch } from "@remix-run/react";
export function CatchBoundary() {
const caught = useCatch();
return (
<div>
<h1>Oops</h1>
<p>Status: {caught.status}</p>
<p>{caught.data.message}</p>
</div>
);
}
export function ErrorBoundary({ error }) {
console.error(error);
return (
<div>
<h1>Uh oh ...</h1>
<p>Something went wrong</p>
<pre>{error.message || "Unknown error"}</pre>
</div>
);
}
import {
useRouteError,
isRouteErrorResponse,
} from "@remix-run/react";
export function ErrorBoundary() {
const error = useRouteError();
// trueの場合 v1の頃の `CatchBoundary` のように機能します
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>Oops</h1>
<p>Status: {error.status}</p>
<p>{error.data.message}</p>
</div>
);
}
// 独自のロジックで型チェックするのを忘れないでください
// エラーに限らず、あらゆる値をスローできます
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>
);
}
formMethod
これは結構シンプルな変更で useNavigation
hookなどにある formMethod
の中身である post
や get
などといったフォームのメソッドが POST
や GET
といった風にアッパーケースになるという変更です。useNavigation
に限らず、 formMethod
そのものの仕様が変わるという認識です。
useTransition
これは最近のReact hookとの混同を避けるために非推奨になっています。今後は useNavigation
hookを使いましょう。また、フィールドがなくなり、オブジェクトがオブジェクトそのものにフラット化されました。
import { useTransition } from "@remix-run/react";
function SomeComponent() {
const transition = useTransition();
transition.submission.formData;
transition.submission.formMethod;
transition.submission.formAction;
transition.type;
}
import { useNavigation } from "@remix-run/react";
function SomeComponent() {
const navigation = useNavigation();
// transition.submission keys are flattened onto `navigation[key]`
navigation.formData;
navigation.formMethod;
navigation.formAction;
// this key is removed
navigation.type;
}
フラット化は v2では submission
を間に挟まなくてもよくなったという所だと思います。
useFetcher
こちらに関しては useTransition
と同じようにフィールドが削除され、フラット化されています。
import { useFetcher } from "@remix-run/react";
function SomeComponent() {
const fetcher = useFetcher();
fetcher.submission.formData;
fetcher.submission.formMethod;
fetcher.submission.formAction;
fetcher.type;
}
import { useFetcher } from "@remix-run/react";
function SomeComponent() {
const fetcher = useFetcher();
// these keys are flattened
fetcher.formData;
fetcher.formMethod;
fetcher.formAction;
// this key is removed
fetcher.type;
}
Links
と imagesizes
imagesrcset
v1ではlowercaseが許容されていましたが、v2からはcamelCaseを使用する必要があります。
export const links: LinksFunction = () => {
return [
{
rel: "preload",
as: "image",
imagesrcset: "...",
imagesizes: "...",
},
];
};
型も LinksFunction
から V2_LinksFunction
に変わっています。
export const links: V2_LinksFunction = () => {
return [
{
rel: "preload",
as: "image",
imageSrcSet: "...", // camelCaseになっている
imageSizes: "...", // camelCaseになっている
},
];
};
remix.config.jsに関する変更
先に軽くまとめておきます。
変更前 | 変更後 | 備考 |
---|---|---|
browserBuildDirectory | assetsBuildDirectory | |
serverBuildDirectory | serverBuildPath | ディレクトリではなく、モジュールに対するパスを指定する必要があります |
serverBuildDirectory
変更前
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
serverBuildDirectory: "./build",
};
変更後
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
serverBuildPath: "./build/index.js",
};
serverBuildTarget
serverBuildTarget
を指定する代わりに デプロイ先のサービス名などを指定することで、簡単にそのサーバーが求めるコードを生成できるということのようです。
これに関しては私はあまり使ったことが無いので、詳しく知りたい方は以下のドキュメントをご覧ください。
Dev Server
v2のdev サーバーはHMRとHDRなどをサポートしており、非常にクールな機能となっています。今まで開発している最中、フォームのコンポーネントに変更を加えた場合、ページ自体がリロードされフォームの内容などは消えてしまっていましたが、v2からは変更した箇所のみを更新することが可能となっており、フォームのコンポーネントを更新しても入力した内容はそのままというものです。
これは何もフロントだけの話ではなく、 loader
内などで prismaなどを用いてデータ取得している場合、なんとそれもHMRによってページをリロードすることなく、対象の箇所だけ変更可能となっています。個人的に今回紹介する物の中で一番クールな機能だと思います。
より詳しく知りたい!という方は以下のRemix公式の動画でどのような機能か確認しましょう!
Remix関連のライブラリについて
remix-utils
や remix-auth
を使用している場合はバージョンを上げましょう。更新が止まっているものはForkするか更新が再開するまでv2へのアップデートを見送るのがいいと思います。
最後に
ここまでお読みいただきありがとうございました。Remixはまだ日本での情報などが少なく、開発していく中で良く分からないところも出てくるかもしれませんが公式Discordやドキュメントなどを読んでいくことでそれなりにどうにかなるので、ぜひ挑戦してみてください。一応Misskeyもやっているので、分からないことがあって直接聞きたかったりしたらこのアカウントにメンションを飛ばしてくだされば返信できます。
また、過去に私が執筆した記事でv1 routeの時にはなりますが、どのように各種機能などを使うか等をまとめているので、よければそちらもご覧ください。改めてここまで読んでいただきありがとうございました。
リンク集
今回記事の中で紹介したv2 routeに関する公式ドキュメント
Remixの公式Discord
私が過去に執筆したRemixの記事
Discussion