12k
All articles

Серверный рендеринг в Solid с помощью SolidStart

Создайте приложение SolidStart v2: серверный рендеринг данных, query и createAsync, предварительная загрузка маршрутов, серверные функции и развёртывание.

OpenReplay Team
OpenReplay Team
Серверный рендеринг в Solid с помощью SolidStart

SolidStart v2 (@solidjs/start 2.x) · Solid v1 · Node.js 24+ · Vite 8

SolidStart — это full-stack-фреймворк для SolidJS. Он добавляет к компонентам Solid файловую маршрутизацию, серверный рендеринг и серверные функции. Для Solid он играет ту же роль, что Next.js для React и SvelteKit для Svelte.

Если вы уже пишете компоненты на Solid, то при переходе к full-stack главная трудность — туториалы. Большинство из них до сих пор используют app.config.ts и Vinxi, хотя в текущей мажорной версии нет ни того, ни другого. В этой статье мы соберём небольшое приложение на SolidStart v2 с загрузкой данных на сервере и страницей с подробностями, использующей предзагрузку. Затем разберём серверные функции, настройку рендеринга и деплой. Также обсудим, достаточно ли зрела экосистема, чтобы предпочесть её стеку на React. Предполагается, что вы знакомы с паттернами уровня компонентов, описанными в статье SolidJS best practices.

Ключевые выводы

  • SolidStart v2 стал стабильным 4 августа 2026 года. Он рассчитан на Solid v1, заменяет Vinxi на Environment API из Vite, требует Node.js 24+ и Vite 8 и настраивается в vite.config.ts.
  • Данные маршрута в SolidStart v2 загружаются так: асинхронная функция оборачивается в query из @solidjs/router и читается через createAsync.
  • Директива "use server" в начале тела запроса заставляет функцию выполняться на сервере, где ей доступны секреты и данные сессии.
  • Экспорт route.preload из модуля маршрута запускает запрос маршрута ещё до рендеринга компонента страницы.
  • SolidStart v2 деплоится через плагины Vite (Nitro v3, Cloudflare, Netlify) и не поддерживает собственные адаптеры для хостинга.

Что такое SolidStart и в каком состоянии находится v2?

SolidStart — это слой метафреймворка поверх Solid. Solid рендерит компоненты, а SolidStart добавляет вокруг них маршрутизацию, серверный рендеринг, загрузку данных и конвейер сборки и деплоя.

SolidStart v2 стал стабильным 4 августа 2026 года. Vinxi больше нет: v2 построен на Environment API из Vite. Потребуются Node.js 24 или новее и Vite 8, а настройки фреймворка теперь находятся в vite.config.ts, а не в app.config.ts. v2 создан для Solid v1. В анонсе Release Candidate Solid 2.0 говорится, что SolidStart теперь получает только поддерживающие релизы. В Solid 2.0 его роль возьмёт на себя режим Start, встроенный в ядро Solid. Приложения, уже работающие на SolidStart в продакшене, продолжат работать. Код ниже рассчитан на Solid v1 и SolidStart v2. Если вы обновляете существующее приложение на v1, воспользуйтесь руководством по миграции с v1.

Создание проекта на SolidStart

Чтобы создать каркас проекта, запустите инициализатор Solid и выберите шаблон SolidStart:

npm init solid
# or: pnpm create solid
cd my-app
npm install

Приложение настраивается в vite.config.ts. Минимальная конфигурация из руководства по началу работы выглядит так:

import { defineConfig } from "vite";
import { solidStart } from "@solidjs/start/config";

export default defineConfig({
  plugins: [solidStart()],
});

Для продакшена solidStart() используется в паре с серверным плагином, например Nitro v3. Согласно справочнику по конфигурации solidStart, настройки Nitro не передаются в solidStart(), а задаются в отдельном ключе верхнего уровня nitro.

Каждый файл в src/routes становится либо страницей, либо API-эндпоинтом. На странице об API-маршрутах в v2 объясняется, что эндпоинты подчиняются тем же правилам сопоставления файлов и путей, что и страницы. Встроенный алиас ~ указывает на корень приложения, поэтому при настройках по умолчанию ~/lib/db соответствует src/lib/db.

Загрузка данных на сервере с помощью query и createAsync

В SolidStart данные маршрута поступают из асинхронной функции, обёрнутой в query из @solidjs/router. Компонент затем читает их через createAsync. Именно этот паттерн используется в документации v2 по получению данных. Там же сказано, что SolidStart v2 работает как с Solid Router v1, так и с TanStack Solid Router v1. В примерах ниже используется Solid Router.

Начнём с модуля данных src/lib/posts.ts. В нём используется массив в памяти, поэтому пример запускается без базы данных:

export type Post = { id: string; title: string; body: string };

const posts: Post[] = [
  { id: "1", title: "Hello SolidStart", body: "First post." },
  { id: "2", title: "Server functions", body: "Second post." },
];

export async function listPosts() {
  return posts.map(({ id, title }) => ({ id, title }));
}

export async function findPost(id: string) {
  return posts.find((p) => p.id === id) ?? null;
}

Маршрут со списком постов:

import { For } from "solid-js";
import { createAsync, query, type RouteDefinition } from "@solidjs/router";
import { listPosts } from "~/lib/posts";

const getPostList = query(async () => {
  "use server";
  return listPosts();
}, "post-list");

export const route = {
  preload: () => getPostList(),
} satisfies RouteDefinition;

export default function PostsPage() {
  const list = createAsync(() => getPostList());
  return (
    <ul>
      <For each={list()}>
        {(post) => (
          <li>
            <a href={`/posts/${post.id}`}>{post.title}</a>
          </li>
        )}
      </For>
    </ul>
  );
}

Страница с подробностями — это динамический маршрут, принимающий параметр id. Синтаксис имён файлов для динамических сегментов описан в документации v2 по маршрутизации.

import { Show } from "solid-js";
import { createAsync, query, type RouteDefinition } from "@solidjs/router";
import { findPost } from "~/lib/posts";

const getPostById = query(async (id: string) => {
  "use server";
  return findPost(id);
}, "post-by-id");

export const route = {
  preload: ({ params }) => getPostById(params.id),
} satisfies RouteDefinition;

export default function PostPage(props: { params: { id: string } }) {
  const post = createAsync(() => getPostById(props.params.id));
  return (
    <Show when={post()} fallback={<p>Post not found.</p>}>
      {(p) => (
        <article>
          <h1>{p().title}</h1>
          <p>{p().body}</p>
        </article>
      )}
    </Show>
  );
}

Второй аргумент query — это ключ кэша, поэтому у каждого запроса должен быть свой ключ. Экспорт route.preload запускает запрос до рендеринга компонента страницы, то есть загрузка данных начинается одновременно с навигацией.

Формы, мутации и ревалидация

Запись данных на сервер устроена иначе, чем их чтение. Мутация должна изменить состояние на сервере, а затем сообщить странице, что закэшированные данные устарели. В v2 запись выполняется через action из Solid Router. Если работа должна происходить на сервере, в тело action добавляется директива "use server". Актуальные примеры форм, обработка состояний ожидания и ошибок, а также передача дополнительных аргументов через .with() описаны на странице v2 о мутациях данных.

Сторона чтения при ревалидации работает так же, как в предыдущем разделе. Каждый query кэшируется под своим ключом (выше это "post-list" и "post-by-id"), а createAsync перечитывает данные, когда эта запись в кэше ревалидируется. После успешного выполнения action Solid Router по умолчанию ревалидирует все активные запросы на странице. Чтобы обновить только часть из них, передайте ключи в revalidate: getPostList.key затрагивает все экземпляры запроса, а getPostById.keyFor(id) — только один. Документация по получению данных отсылает вопросы поведения кэша к Solid Router, и подробно оно описано на странице о ревалидации в документации роутера.

Понятные и стабильные имена ключей запросов окупаются, когда нужно сузить ревалидацию. Мутации, добавляющей пост, достаточно обновить "post-list", а при редактировании нужно обновить ещё и "post-by-id".

Что делает “use server”?

Директива "use server" в начале тела функции заставляет эту функцию выполняться на сервере, даже если она вызывается из компонента. Поэтому помеченный так запрос может читать секреты из process.env и данные сессии. В документации v2 по получению данных это показано на примере серверного кода с useSession из @solidjs/start/http.

Помещайте внутрь "use server"Не помещайте туда
Секреты окружения (process.env.*)Браузерные API (window, localStorage, DOM)
Доступ к базе данных и сессииUI-состояние, которое живёт в компонентах
Хелперы из @solidjs/start/httpВсё, что должно выполняться по клику без запроса к серверу

В приложении на SolidStart UI-состояние принадлежит клиенту. Сервер хранит данные, а сигналы и сторы — состояние взаимодействия. Это разделение подробно описано в статье state management in SolidJS.

SolidStart v2 также распознаёт импорты-маркеры server-only и client-only. Если добавить такой маркер в модуль, то импорт этого модуля не с той стороны (серверного кода на клиенте или наоборот) приведёт к ошибке dev-сервера или сборки, и ошибка не дойдёт до пользователей. В справочнике по конфигурации указано, что @solidjs/start/http и @solidjs/start/middleware уже помечены как server-only. Кроме того, в режиме разработки доступна панель инструментов, которая показывает ошибки приложения и вызовы серверных функций. В продакшен-сборку она никогда не попадает.

Режимы рендеринга в SolidStart

В SolidStart v2 поведение рендеринга настраивается через плагин solidStart() и парный ему плагин деплоя, которые объявляются в vite.config.ts. Концептуальные компромиссы между серверным рендерингом, стримингом и статической генерацией разобраны в статье top 10 rendering patterns. Здесь мы рассмотрим лишь то, где эти настройки задаются в SolidStart.

Поскольку app.config.ts больше не существует, флаги рендеринга из эпохи v1, которые встречаются в старых статьях, в исходном виде не применимы. Доступные в v2 опции смотрите в справочнике по конфигурации solidStart() и в документации плагинов деплоя.

Когда страница рендерится на сервере, пользователь видит разметку до того, как клиентский JavaScript выполнит гидратацию. Записи пользовательских сессий (session replay) в SSR-приложениях хорошо показывают этот разрыв: пользователь нажимает кнопку, которая отрисована корректно, но ничего не делает, и нажимает снова, когда обработчики уже подключены.

Деплой SolidStart v2

У SolidStart v2 нет собственных адаптеров для хостинга. Он деплоится через плагины Vite, и в анонсе v2 названы три поддерживаемые платформы: плагин Nitro v3 для Vite (по данным анонса, пока в бете), плагин Cloudflare для Vite и плагин Netlify для Vite. В руководстве по началу работы есть ссылка на страницу о плагинах деплоя, где для каждого из них описана настройка продакшен-окружения.

Реальная цена выбора SolidStart вместо стека на React — экосистема. У React гораздо больше сторонних библиотек компонентов, интеграций для аутентификации и шаблонов для хостинга, и командам на Solid приходится писать больше связующего кода самостоятельно. В сравнении SolidJS и React этот разрыв сопоставляется с преимуществами модели рендеринга Solid. Учитывайте также, что SolidStart v2 находится в режиме поддержки, пока Solid 2.0 движется к режиму Start.

Заключение

SolidStart v2 — стабильный, нативный для Vite способ добавить серверный рендеринг в приложения на Solid v1. Загрузка данных сводится к трём элементам: query, createAsync и директиве "use server", а route.preload обеспечивает раннюю загрузку. Следующий шаг — создать проект командой npm init solid, добавить два маршрута для постов из примеров выше, а затем подключить мутацию, используя раздел документации v2 о мутациях данных.

Часто задаваемые вопросы

Работает ли SolidStart v2 с Solid 2.0?

Нет. SolidStart v2 создан для Solid 1.x, и ни один релиз SolidStart не рассчитан на Solid 2.0. В Release Candidate Solid 2.0 SolidStart выводится из употребления, а его составные части переносятся в другие места: серверные функции теперь обрабатывает ядро Solid, обслуживанием запросов занимается режим Start, а файловую маршрутизацию — отдельный пакет, не привязанный к конкретному роутеру. Существующие приложения на SolidStart продолжают работать, SolidStart по-прежнему получает поддерживающие релизы, а переход на режим Start описан в официальном руководстве по миграции.

Чем TanStack Start отличается от SolidStart с TanStack Router?

Это разные варианты. SolidStart v2 не привязан к конкретному роутеру и работает как с официальным Solid Router v1, так и с TanStack Solid Router v1, поэтому маршрутизацию TanStack можно использовать, не отказываясь от SolidStart. TanStack Start — отдельный метафреймворк с вариантом для Solid, который публикуется как @tanstack/solid-start, и у него уже есть бета-релиз для Solid 2.0. Для Solid 2.0 стартовый шаблон fullstack-tanstack объединяет режим Start с TanStack Router и TanStack Query.

Что такое single-flight-мутации в SolidStart?

Single-flight-мутации возвращают обновлённые данные в том же ответе сервера, что и результат самой мутации. Когда серверный action изменяет данные, предзагруженные текущим представлением, или выполняет редирект на страницу с предзагруженными данными, SolidStart ревалидирует эти данные на сервере и отправляет их за тот же сетевой цикл. Клиенту не нужно делать второй набор запросов, поэтому после записи не возникает водопада запросов.

Что меняется при обновлении приложения с SolidStart v1 на v2?

Для обновления нужны Node.js 24 и Vite 8. Конфигурация фреймворка переносится из app.config.ts в vite.config.ts, где solidStart() регистрируется как плагин Vite, а Environment API из Vite заменяет Vinxi. В официальном руководстве по миграции также описаны изменения в путях импорта, типах окружения TypeScript, серверном рантайме и middleware. Сверяйте пути импорта и код middleware из v1 с руководством, а не переносите их как есть.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.