Remix v2 Overview
Remix v2 is a full-stack React framework that leverages web fundamentals (HTTP, forms, browser caching). v2 features Vite integration, improved type safety, and performance improvements.
Vite Integration
Configuration
// vite.config.ts
import { vitePlugin as remix } from '@remix-run/dev';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
remix({
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true
}
})
]
});
Development Server
npm run dev
# Vite's fast HMR available
File-Based Routing
app/
├── routes/
│ ├── _index.tsx # /
│ ├── about.tsx # /about
│ ├── posts._index.tsx # /posts
│ ├── posts.$postId.tsx # /posts/:postId
│ ├── posts.$postId_.edit.tsx # /posts/:postId/edit
│ └── ($lang).docs.$.tsx # /:lang?/docs/*
└── root.tsx
Route Naming Conventions
| Pattern | Meaning |
|---|---|
| _index | Index route |
| $param | Dynamic parameter |
| ($param) | Optional parameter |
| $ | Splat (catch-all) |
| _ | Route without layout |
| . | Nested URL |
Data Loading
loader
// routes/posts.$postId.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
export async function loader({ params }: LoaderFunctionArgs) {
const post = await db.post.findUnique({
where: { id: params.postId }
});
if (!post) {
throw new Response('Not Found', { status: 404 });
}
return json({ post });
}
export default function Post() {
const { post } = useLoaderData<typeof loader>();
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
action
// routes/posts.new.tsx
import { redirect, type ActionFunctionArgs } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { z } from 'zod';
const schema = z.object({
title: z.string().min(1),
content: z.string().min(10)
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const result = schema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return json({ errors: result.error.flatten() }, { status: 400 });
}
const post = await db.post.create({ data: result.data });
return redirect(`/posts/${post.id}`);
}
export default function NewPost() {
const actionData = useActionData<typeof action>();
return (
<Form method="post">
<input name="title" />
{actionData?.errors?.fieldErrors?.title && (
<span>{actionData.errors.fieldErrors.title}</span>
)}
<textarea name="content" />
<button type="submit">Create</button>
</Form>
);
}
fetcher
Submit and retrieve data without navigation.
import { useFetcher } from '@remix-run/react';
export default function LikeButton({ postId }: { postId: string }) {
const fetcher = useFetcher();
const isLiking = fetcher.state === 'submitting';
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>
);
}
defer and Suspense
import { defer } from '@remix-run/node';
import { Await, useLoaderData } from '@remix-run/react';
import { Suspense } from 'react';
export async function loader() {
// Immediately needed data
const user = await getUser();
// Deferred data
const recommendations = getRecommendations(); // Don't await
return defer({
user,
recommendations
});
}
export default function Dashboard() {
const { user, recommendations } = useLoaderData<typeof loader>();
return (
<div>
<h1>Welcome, {user.name}</h1>
<Suspense fallback={<div>Loading recommendations...</div>}>
<Await resolve={recommendations}>
{(data) => <RecommendationsList items={data} />}
</Await>
</Suspense>
</div>
);
}
Error Handling
// routes/posts.$postId.tsx
import { isRouteErrorResponse, useRouteError } 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>
);
}
Metadata
import { type MetaFunction } from '@remix-run/node';
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{ title: data?.post.title ?? 'Not Found' },
{ name: 'description', content: data?.post.excerpt },
{ property: 'og:title', content: data?.post.title }
];
};
Deployment
# Node.js
npm run build
npm start
# Cloudflare Pages
npm run build
npx wrangler pages deploy ./build/client
# Vercel
npm run build
# Configure in vercel.json
Summary
Remix v2 is a robust full-stack framework that leverages web fundamentals. With Vite integration for improved developer experience, type-safe data loading, and progressive enhancement, you can efficiently build modern web applications.
← Back to list