Перейти к содержимому

2. Установка и структура

Начать работу с Remix очень просто — официальный CLI создаст всё необходимое за несколько минут.

Окно терминала
npx create-remix@latest my-app

CLI задаст несколько вопросов:

  • Где разместить проект?
  • Использовать TypeScript?
  • Установить зависимости?

После установки:

Окно терминала
cd my-app
npm run dev
# → http://localhost:3000
my-app/
├── app/
│ ├── routes/ # Файловая маршрутизация
│ │ ├── _index.tsx # → /
│ │ ├── about.tsx # → /about
│ │ └── blog.$id.tsx # → /blog/:id
│ ├── components/ # Переиспользуемые компоненты
│ ├── utils/ # Утилиты
│ ├── entry.client.tsx # Точка входа на клиенте
│ ├── entry.server.tsx # Точка входа на сервере
│ └── root.tsx # Корневой layout
├── public/ # Статические файлы
├── remix.config.js # Конфигурация Remix (v1)
├── vite.config.ts # Конфигурация Vite (v2)
├── tsconfig.json
└── package.json

Корневой компонент всего приложения. Содержит <html>, <head>, <body>:

import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
export default function Root() {
return (
<html lang="ru">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

Обрабатывает серверный рендеринг. Обычно не нужно трогать.

import { renderToPipeableStream } from "react-dom/server";
import { RemixServer } from "@remix-run/react";
export default function handleRequest(request, responseStatusCode, responseHeaders, remixContext) {
// renderToPipeableStream для streaming SSR
}

Гидратирует серверный HTML на клиенте:

import { RemixBrowser } from "@remix-run/react";
import { hydrateRoot } from "react-dom/client";
hydrateRoot(document, <RemixBrowser />);
{
"scripts": {
"build": "remix vite:build",
"dev": "remix vite:dev",
"lint": "eslint --ignore-path .gitignore .",
"start": "remix-serve ./build/server/index.js",
"typecheck": "tsc"
}
}
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,
v3_throwAbortReason: true,
},
}),
],
});