Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Ресурсные маршруты: 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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.