remix
TypeScriptを活用したRemix開発において、loader/actionの設計、ネストされたルート構成、フルスタックWebアプリケーションのベストプラクティスに関する専門的なガイダンスを提供します。Remixプロジェクトの構築・最適化時に活用できます。
description の原文を見る
Expert guidance for Remix development with TypeScript, loaders/actions, nested routes, and full-stack web application best practices
SKILL.md 本文
Remix Development
Remix、React、TypeScript、フルスタックウェブ開発の専門家です。
主要原則
- 正確なTypeScriptの例を含む簡潔で技術的なRemixコードを記述する
- プログレッシブエンハンスメントとウェブ標準を活用する
- データ取得にはloadersを、ミューテーションにはactionsを使用する
- ネストされたルートをコード構成に活用する
- サーバーサイドレンダリングとウェブの基礎を優先する
プロジェクト構成
app/
├── components/ # 再利用可能なReactコンポーネント
├── models/ # データベースモデルと型
├── routes/
│ ├── _index.tsx # / ルート
│ ├── about.tsx # /about ルート
│ └── posts/
│ ├── _index.tsx # /posts ルート
│ └── $slug.tsx # /posts/:slug ルート
├── styles/ # CSSファイル
├── utils/ # ユーティリティ関数
├── entry.client.tsx # クライアントエントリーポイント
├── entry.server.tsx # サーバーエントリーポイント
└── root.tsx # ルートレイアウト
Loaders
基本的なLoader
import type { LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
export async function loader({ params }: LoaderFunctionArgs) {
const post = await getPost(params.slug);
if (!post) {
throw new Response('Not Found', { status: 404 });
}
return json({ post });
}
export default function PostRoute() {
const { post } = useLoaderData<typeof loader>();
return <article>{post.content}</article>;
}
認証付きLoader
import { redirect } from '@remix-run/node';
import { getUser } from '~/utils/session.server';
export async function loader({ request }: LoaderFunctionArgs) {
const user = await getUser(request);
if (!user) {
throw redirect('/login');
}
return json({ user });
}
Actions
フォーム処理
import type { ActionFunctionArgs } from '@remix-run/node';
import { json, redirect } from '@remix-run/node';
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const title = formData.get('title');
const content = formData.get('content');
const errors: Record<string, string> = {};
if (!title) {
errors.title = 'Title is required';
}
if (Object.keys(errors).length > 0) {
return json({ errors }, { status: 400 });
}
const post = await createPost({ title, content });
return redirect(`/posts/${post.slug}`);
}
Action データの使用
import { useActionData, Form } from '@remix-run/react';
export default function NewPost() {
const actionData = useActionData<typeof action>();
return (
<Form method="post">
<input name="title" type="text" />
{actionData?.errors?.title && (
<p className="error">{actionData.errors.title}</p>
)}
<textarea name="content" />
<button type="submit">Create Post</button>
</Form>
);
}
ネストされたルート
レイアウトルート
// routes/dashboard.tsx (レイアウト)
import { Outlet } from '@remix-run/react';
export default function DashboardLayout() {
return (
<div className="dashboard">
<nav>
<Link to="/dashboard">Overview</Link>
<Link to="/dashboard/settings">Settings</Link>
</nav>
<main>
<Outlet />
</main>
</div>
);
}
パスレスレイアウト
// routes/_auth.tsx (パスレスレイアウト: /login, /register用)
export default function AuthLayout() {
return (
<div className="auth-container">
<Outlet />
</div>
);
}
useFetcher
ナビゲーションなしのフェッチ
import { useFetcher } from '@remix-run/react';
export default function LikeButton({ postId }: { postId: string }) {
const fetcher = useFetcher();
const isLiking = fetcher.state !== 'idle';
return (
<fetcher.Form method="post" action="/api/like">
<input type="hidden" name="postId" value={postId} />
<button type="submit" disabled={isLiking}>
{isLiking ? 'Liking...' : 'Like'}
</button>
</fetcher.Form>
);
}
オプティミスティックUI
export default function TodoItem({ todo }: { todo: Todo }) {
const fetcher = useFetcher();
const isDeleting = fetcher.formData?.get('_action') === 'delete';
if (isDeleting) {
return null; // オプティミスティックに削除
}
return (
<li>
{todo.title}
<fetcher.Form method="post">
<input type="hidden" name="_action" value="delete" />
<input type="hidden" name="id" value={todo.id} />
<button type="submit">Delete</button>
</fetcher.Form>
</li>
);
}
エラーバウンダリ
import { useRouteError, isRouteErrorResponse } from '@remix-run/react';
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
return (
<div>
<h1>Error</h1>
<p>{error instanceof Error ? error.message : 'Unknown error'}</p>
</div>
);
}
リソースルート
// routes/api/posts.tsx
import { json } from '@remix-run/node';
export async function loader() {
const posts = await getPosts();
return json(posts);
}
// デフォルトエクスポートなし = リソースルート (UI なし)
Meta と Links
import type { MetaFunction, LinksFunction } from '@remix-run/node';
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{ title: data?.post.title ?? 'Blog' },
{ name: 'description', content: data?.post.excerpt },
];
};
export const links: LinksFunction = () => {
return [
{ rel: 'stylesheet', href: styles },
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
];
};
セッション管理
// utils/session.server.ts
import { createCookieSessionStorage, redirect } from '@remix-run/node';
const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true,
path: '/',
sameSite: 'lax',
secrets: [process.env.SESSION_SECRET!],
secure: process.env.NODE_ENV === 'production',
},
});
export async function createUserSession(userId: string, redirectTo: string) {
const session = await sessionStorage.getSession();
session.set('userId', userId);
return redirect(redirectTo, {
headers: {
'Set-Cookie': await sessionStorage.commitSession(session),
},
});
}
パフォーマンス
<Link prefetch="intent">でプリフェッチする- ストリーミングデータに
deferを使用する - ヘッダーで stale-while-revalidate を実装する
- 動的インポートでコード分割する
- ローダーレスポンスを適切にキャッシュする
ベストプラクティス
- 常にサーバーでフォームデータを検証する
- 型安全性のため TypeScript を使用する
- ローディング状態とエラー状態を処理する
- 適切な CSRF 保護を実装する
- プログレッシブエンハンスメントを使用する
- JavaScript を無効にした状態でテストする
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mindrally
- リポジトリ
- mindrally/skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/mindrally/skills / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。