npx skills add https://github.com/mindrally/skills --skill remix您是一位精通 Remix、React、TypeScript 和全栈 Web 开发的专家。
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 # 根布局
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>;
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
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 });
}
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 = '标题不能为空';
}
if (Object.keys(errors).length > 0) {
return json({ errors }, { status: 400 });
}
const post = await createPost({ title, content });
return redirect(`/posts/${post.slug}`);
}
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">创建文章</button>
</Form>
);
}
// routes/dashboard.tsx (布局)
import { Outlet } from '@remix-run/react';
export default function DashboardLayout() {
return (
<div className="dashboard">
<nav>
<Link to="/dashboard">概览</Link>
<Link to="/dashboard/settings">设置</Link>
</nav>
<main>
<Outlet />
</main>
</div>
);
}
// routes/_auth.tsx (用于 /login, /register 的无路径布局)
export default function AuthLayout() {
return (
<div className="auth-container">
<Outlet />
</div>
);
}
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 ? '点赞中...' : '点赞'}
</button>
</fetcher.Form>
);
}
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">删除</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>错误</h1>
<p>{error instanceof Error ? error.message : '未知错误'}</p>
</div>
);
}
// routes/api/posts.tsx
import { json } from '@remix-run/node';
export async function loader() {
const posts = await getPosts();
return json(posts);
}
// 没有默认导出 = 资源路由 (无 UI)
import type { MetaFunction, LinksFunction } from '@remix-run/node';
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{ title: data?.post.title ?? '博客' },
{ 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 实现数据流式传输每周安装量
105
代码仓库
GitHub 星标数
43
首次出现
2026 年 1 月 25 日
安全审计
安装于
opencode86
gemini-cli85
github-copilot78
codex78
cursor78
amp71
You are an expert in Remix, React, TypeScript, and full-stack web development.
app/
├── components/ # Reusable React components
├── models/ # Database models and types
├── routes/
│ ├── _index.tsx # / route
│ ├── about.tsx # /about route
│ └── posts/
│ ├── _index.tsx # /posts route
│ └── $slug.tsx # /posts/:slug route
├── styles/ # CSS files
├── utils/ # Utility functions
├── entry.client.tsx # Client entry
├── entry.server.tsx # Server entry
└── root.tsx # Root layout
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>;
}
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 });
}
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}`);
}
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 (layout)
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 (pathless layout for /login, /register)
export default function AuthLayout() {
return (
<div className="auth-container">
<Outlet />
</div>
);
}
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>
);
}
export default function TodoItem({ todo }: { todo: Todo }) {
const fetcher = useFetcher();
const isDeleting = fetcher.formData?.get('_action') === 'delete';
if (isDeleting) {
return null; // Optimistically remove
}
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);
}
// No default export = resource route (no UI)
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 for streaming dataWeekly Installs
105
Repository
GitHub Stars
43
First Seen
Jan 25, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode86
gemini-cli85
github-copilot78
codex78
cursor78
amp71
UI组件模式实战指南:构建可复用React组件库与设计系统
10,700 周安装