Ресурсные маршруты: JSON и файлы
Не каждый адрес — страница. Мобильному приложению нужен список статей в JSON, бухгалтеру — выгрузка в CSV, читалкам — RSS-лента. Для этого в React Router есть ресурсные маршруты (resource routes): модуль маршрута без компонента по умолчанию. Его loader или action возвращает готовый HTTP-ответ, и фреймворк отдаёт его как есть, без HTML-каркаса.
JSON
Добавьте в app/routes.ts маршрут вне макета, чтобы не было лишнего загрузчика шапки:
// app/routes.ts
route("api/posts", "routes/api.posts.ts"),Создайте файл app/routes/api.posts.ts — обратите внимание на расширение .ts, JSX здесь не нужен:
// app/routes/api.posts.ts
import { getPosts } from "../data.server";
export async function loader() {
const posts = await getPosts();
return Response.json(posts);
}Откройте http://localhost:5173/api/posts — браузер покажет массив статей в формате JSON. Response.json — стандартный метод, который сам ставит заголовок Content-Type: application/json.
Приём данных
Ресурсный маршрут может принимать запросы через action:
// app/routes/api.posts.ts
import type { Route } from "./+types/api.posts";
import { createPost } from "../data.server";
export async function action({ request }: Route.ActionArgs) {
const { title, body } = await request.json();
const post = await createPost(String(title), String(body));
return Response.json(post, { status: 201 });
}Запрос POST с телом в JSON на /api/posts создаст статью и вернёт её с кодом 201. Проверить можно командой:
curl -X POST http://localhost:5173/api/posts -H "Content-Type: application/json" -d '{"title":"Из API","body":"Текст"}'Файл для скачивания
Ответ может быть любым — достаточно указать нужные заголовки:
// app/routes/posts.csv.ts
import { getPosts } from "../data.server";
export async function loader() {
const posts = await getPosts();
const lines = ["id;title", ...posts.map((p) => `${p.id};${p.title}`)];
return new Response(lines.join("\n"), {
headers: {
"Content-Type": "text/csv; charset=utf-8",
"Content-Disposition": 'attachment; filename="posts.csv"',
},
});
}С маршрутом route("posts.csv", "routes/posts.csv.ts") адрес /posts.csv скачает файл со списком статей.
Ссылка на ресурс
// app/routes/home.tsx
<Link to="/posts.csv" reloadDocument>Скачать CSV</Link>Проп reloadDocument говорит ссылке выполнить обычный переход браузера, а не загрузку данных маршрута.
Частая ошибка
Сослаться на ресурсный маршрут обычным Link без reloadDocument. Роутер попытается отрисовать «страницу» у маршрута, у которого нет компонента, и покажет ошибку. Для ресурсов используйте Link с reloadDocument или простой тег a.