4. Loaders: загрузка данных
loader — это серверная функция в Remix, которая выполняется перед рендерингом страницы. Она загружает данные из БД, API или любого другого источника, и передаёт их компоненту через useLoaderData.
Базовый loader
Заголовок раздела «Базовый loader»import { json } from "@remix-run/node";import { useLoaderData } from "@remix-run/react";
// Серверная функция — выполняется ТОЛЬКО на сервереexport async function loader() { const users = await db.user.findMany(); return json(users);}
// Клиентский компонент — получает данные из loaderexport default function Users() { const users = useLoaderData<typeof loader>(); return ( <ul> {users.map(user => <li key={user.id}>{user.name}</li>)} </ul> );}Параметры loader
Заголовок раздела «Параметры loader»export async function loader({ request, params, context }) { // params — динамические сегменты URL (:id, :slug) const { id } = params;
// request — полный Request объект (URL, headers, cookies) const url = new URL(request.url); const search = url.searchParams.get("q");
// context — данные из адаптера (env переменные и т.д.) const user = await db.user.findUnique({ where: { id } });
if (!user) { throw new Response("Not Found", { status: 404 }); }
return json({ user, search });}TypeScript типизация
Заголовок раздела «TypeScript типизация»import type { LoaderFunctionArgs } from "@remix-run/node";
export async function loader({ params }: LoaderFunctionArgs) { const post = await getPost(params.slug!); return json({ post });}
// Автовывод типов — TypeScript знает структуру данныхexport default function Post() { const { post } = useLoaderData<typeof loader>(); // post имеет правильный тип! return <h1>{post.title}</h1>;}Обработка ошибок
Заголовок раздела «Обработка ошибок»export async function loader({ params }) { const user = await db.user.findUnique({ where: { id: params.id } });
// 404 — бросаем Response if (!user) { throw new Response("Пользователь не найден", { status: 404 }); }
return json(user);}Headers и кэширование
Заголовок раздела «Headers и кэширование»export async function loader() { const data = await getStaticData(); return json(data, { headers: { "Cache-Control": "max-age=3600, stale-while-revalidate=86400", }, });}defer — стриминг данных
Заголовок раздела «defer — стриминг данных»import { defer } from "@remix-run/node";import { Await } from "@remix-run/react";import { Suspense } from "react";
export async function loader() { // Быстрые данные — ждём const user = await getUser(); // Медленные данные — стримим const posts = getSlowPosts(); // Promise, без await!
return defer({ user, posts });}
export default function Page() { const { user, posts } = useLoaderData<typeof loader>(); return ( <div> <h1>{user.name}</h1> <Suspense fallback={<p>Загрузка постов...</p>}> <Await resolve={posts}> {(data) => <PostList posts={data} />} </Await> </Suspense> </div> );}