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

18. SolidStart: мета-фреймворк

Яша, SolidStart — это как Next.js для React, но для Solid.js. И он по-настоящему крутой! File-based роутинг, серверные функции прямо рядом с клиентским кодом, streaming SSR, и всё это на базе Vinxi — нового build-инструмента. Давай разберёмся! ⚡


Окно терминала
npm create solid@latest
# Структура проекта
my-app/
├── src/
├── routes/ # Файловый роутинг
├── index.tsx # → /
├── about.tsx # → /about
├── blog/
├── index.tsx # → /blog
└── [slug].tsx # → /blog/:slug
└── (auth)/ # Route group (не влияет на URL)
├── login.tsx # → /login
└── register.tsx # → /register
├── app.tsx # Корневой layout
└── entry-client.tsx # Точка входа клиента
└── entry-server.tsx # Точка входа сервера
├── app.config.ts # Конфиг Vinxi/SolidStart
└── package.json

// src/routes/index.tsx → /
export default function Home() {
return <h1>Главная страница</h1>;
}
// src/routes/blog/[slug].tsx → /blog/:slug
import { useParams } from '@solidjs/router';
export default function BlogPost() {
const params = useParams<{ slug: string }>();
return <h1>Пост: {params.slug}</h1>;
}
// src/routes/[...all].tsx → catch-all (404)
export default function NotFound() {
return <h1>404 — Страница не найдена</h1>;
}
// src/routes/(dashboard)/settings.tsx → /settings
// Скобки создают группу — не влияет на URL, но группирует layout

Главная суперсила SolidStart — функции, которые всегда выполняются на сервере:

src/routes/index.tsx
import { action, query } from '@solidjs/router';
// Server Query — безопасное чтение данных
const getUsers = query(async () => {
'use server'; // 🔑 Эта строка = этот код только на сервере!
const users = await db.query('SELECT * FROM users');
return users;
}, 'users'); // ключ кэша
// Server Action — мутации
const createUser = action(async (formData: FormData) => {
'use server';
const name = formData.get('name') as string;
await db.query('INSERT INTO users (name) VALUES (?)', [name]);
return { success: true };
});
export default function UsersPage() {
const users = createAsync(() => getUsers());
return (
<div>
<For each={users()}>
{(user) => <div>{user.name}</div>}
</For>
<form action={createUser} method="post">
<input name="name" />
<button type="submit">Добавить</button>
</form>
</div>
);
}

🔑 "use server" — директива строка (как "use strict"). Всё что внутри функции с этой директивой никогда не попадёт в клиентский бандл.


// src/routes/api/users.ts → GET /api/users
import { json } from '@solidjs/router';
import type { APIEvent } from '@solidjs/start/server';
export async function GET({ request }: APIEvent) {
const users = await db.getUsers();
return json(users);
}
export async function POST({ request }: APIEvent) {
const body = await request.json();
const user = await db.createUser(body);
return json(user, { status: 201 });
}
// src/routes/api/users/[id].ts → GET /api/users/:id
export async function GET({ params }: APIEvent) {
const user = await db.getUserById(params.id);
if (!user) return new Response(null, { status: 404 });
return json(user);
}

// route.data.ts — загрузчик данных (выполняется на сервере при SSR,
// на клиенте при навигации)
import { cache, createAsync } from '@solidjs/router';
const getProduct = cache(async (id: string) => {
'use server';
return fetch(`https://api.example.com/products/${id}`).then(r => r.json());
}, 'product');
// src/routes/products/[id].tsx
export const route = {
preload: ({ params }) => getProduct(params.id), // предзагрузка
};
export default function ProductPage() {
const params = useParams<{ id: string }>();
const product = createAsync(() => getProduct(params.id));
return (
<Suspense fallback={<Skeleton />}>
<h1>{product()?.name}</h1>
<p>{product()?.price}₽</p>
</Suspense>
);
}

src/entry-server.tsx
// SolidStart поддерживает streaming из коробки!
import { createHandler, StartServer } from '@solidjs/start/server';
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html>
<head>{assets}</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));
// Streaming включается через renderToStream в конфиге

app.config.ts
import { defineConfig } from '@solidjs/start/config';
export default defineConfig({
// Адаптеры для разных хостингов
server: {
preset: 'node', // node | cloudflare | netlify | vercel | bun
// preset: 'cloudflare-pages'
// preset: 'netlify'
},
// Алиасы путей
vite: {
resolve: {
alias: {
'~': '/src',
},
},
},
// SSR настройки
ssr: true, // false = SPA mode
// Islands architecture (экспериментально)
// islands: true,
});

// Файл на сервере — НИКОГДА не попадает в браузер
import { isServer } from 'solid-js/web';
const secret = isServer ? process.env.DB_PASSWORD : undefined;
// Серверная функция — безопасно использует секреты
const getData = query(async () => {
'use server';
// Здесь можно: process.env, БД, файловая система
const data = await prisma.user.findMany({
where: { secret: process.env.SECRET_KEY },
});
return data;
}, 'data');