SolidStart — метафреймворк поверх Solid.js, который добавляет файловый роутинг, серверные функции, SSR и сборку. Solid славится тонкой реактивностью без виртуального DOM, а SolidStart делает его пригодным для полноценных веб-приложений. Разберём создание проекта, работу с данными, сборку и ошибки, которые чаще всего встречаются на практике.
Чем SolidStart отличается от Solid
Сам Solid.js — это библиотека реактивного рендеринга. Она не знает, как устроены маршруты, откуда брать данные и как собирать серверную часть. SolidStart добавляет:
- маршрутизацию по файлам в каталоге
src/routes; - серверные функции с директивой
"use server"; - загрузку данных через
queryи мутации черезaction; - SSR, гидрацию и режим одностраничного приложения;
- пресеты сборки для Node, статики и облачных платформ.
Актуальная ветка — SolidStart 1.x. В основе сборки лежат Vite и серверный рантайм Nitro, поэтому знакомые по экосистеме плагины и пресеты деплоя работают предсказуемо.
Создание проекта
Проще всего начать с официального генератора. Он задаст вопросы о шаблоне и типе приложения, после чего останется установить зависимости.
npm create solid@latest my-app
cd my-app
npm install
npm run dev
Dev-сервер по умолчанию слушает порт 3000. Структура проекта минимальна: src/app.tsx — корневой компонент, src/routes — страницы, app.config.ts — конфигурация сборки. Не добавляйте в проект лишние слои, пока не появится реальная потребность.
Проверить версии установленных пакетов полезно сразу: несовместимость solid-js и @solidjs/start — частая причина странных ошибок гидрации.
npm ls solid-js @solidjs/start @solidjs/router
Маршрутизация по файлам
Файл src/routes/index.tsx отвечает за главную страницу, src/routes/about.tsx — за /about. Динамические сегменты задаются квадратными скобками: src/routes/users/[id].tsx. Вложенные каталоги создают вложенные маршруты, а файл [id].tsx внутри них получает параметр через useParams.
import { useParams } from "@solidjs/router";
export default function UserPage() {
const params = useParams();
return <p>Пользователь: {params.id}</p>;
}
Для страницы 404 достаточно файла [...404].tsx. Не смешивайте маршруты и компоненты в одном каталоге: первые должны оставаться тонкими, а логику выносите в отдельные модули.
Данные: query и action
В SolidStart серверные функции объявляются в отдельном файле с директивой "use server". Чтение оборачивается в query, запись — в action. Такой подход даёт кэширование, повторную валидацию и безопасную границу между клиентом и сервером.
"use server";
import { query } from "@solidjs/router";
export const getPosts = query(async () => {
const res = await fetch("https://api.example.com/posts");
return res.json();
}, "posts");
На странице результат подключается через createAsync и Suspense. Пока данные грузятся, показывайте скелетон, а не пустой экран. Ошибки обрабатывайте через ErrorBoundary: без него сбой запроса уронит всю страницу.
import { createAsync } from "@solidjs/router";
import { Suspense } from "solid-js";
import { getPosts } from "~/lib/posts";
export default function Home() {
const posts = createAsync(() => getPosts());
return (
<Suspense fallback={<p>Загрузка…</p>}>
<ul>
{posts()?.map((p) => <li>{p.title}</li>)}
</ul>
</Suspense>
);
}
Мутации оформляйте через action и обычную форму. Это работает без JavaScript на клиенте и упрощает прогрессивное улучшение. После успешного действия обновите связанный query, иначе пользователь увидит устаревшие данные.
SSR, SPA и статика
По умолчанию приложение рендерится на сервере. Для внутреннего дашборда без SEO иногда проще отключить SSR и получить классическое SPA. Режим задаётся в app.config.ts:
import { defineConfig } from "@solidjs/start/config";
export default defineConfig({
ssr: false,
server: { preset: "node-server" }
});
Для статического сайта используйте пресет static и убедитесь, что все страницы могут быть предрендерены. Если маршрут зависит от cookies или заголовков запроса, статическая генерация для него не подойдёт.
Пресет node-server — универсальный вариант для собственного VPS. Сборка кладёт серверный бандл в .output, и запуск сводится к одной команде.
npm run build
node .output/server/index.mjs
Переменные окружения и секреты
Серверные функции читают переменные через process.env, но на клиенте их не будет. Держите ключи в файле .env локально и в переменных окружения платформы при деплое, а в репозиторий добавляйте только пример .env.example. Публичные значения с префиксом VITE_ попадают в клиентский бандл, поэтому не используйте этот префикс для токенов.
При сборке в контейнере передавайте переменные на этапе запуска, а не только сборки: иначе серверная часть получит пустые значения и упадёт при первом запросе.
Типичные ошибки
- Ошибка гидрации. Причина — разное содержимое на сервере и клиенте: даты, случайные числа, обращение к
windowв теле компонента. - Секреты на клиенте. Всё, что импортировано в клиентский компонент, попадает в бандл. Ключи API держите только в серверных функциях.
- Тяжёлые зависимости. Импорт библиотеки целиком увеличивает бандл; используйте точечные импорты.
- Отсутствие
Suspense. Без него асинхронные данные ведут себя непредсказуемо.
Для проверки типов запускайте npx tsc --noEmit в CI. Это дешевле, чем ловить ошибку в рантайме после деплоя. Логи сборки читайте целиком: предупреждения Nitro часто указывают на несовместимый пресет.
Стоит ли выбирать SolidStart
Фреймворк хорош для команд, которым нужна максимальная производительность при небольшом размере бандла и которые готовы разбираться в реактивной модели. Экосистема заметно меньше, чем у Next.js или Nuxt, поэтому часть готовых решений придётся писать самостоятельно. Зато отладка проще: нет виртуального DOM, а зависимости отслеживаются точечно.
Если проект состоит из простых страниц и форм, выигрыш от SolidStart невелик. Если важны интерактивность, мгновенные обновления и контроль над загрузкой данных, фреймворк раскрывается.
Итог
SolidStart даёт Solid.js полноценную инфраструктуру: маршруты, серверные функции, SSR и предсказуемую сборку. Начать можно с npm create solid@latest, а дальше наращивать проект по мере необходимости. Держите серверные функции отдельно от клиентского кода, не забывайте про Suspense и проверяйте типы в CI. Этого достаточно, чтобы приложение оставалось быстрым и понятным в сопровождении.