18. SolidStart: мета-фреймворк
🚀 SolidStart: мета-фреймворк для Solid.js
Заголовок раздела «🚀 SolidStart: мета-фреймворк для Solid.js»Яша, 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/:slugimport { 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⚡ Серверные функции (“use server”)
Заголовок раздела «⚡ Серверные функции (“use server”)»Главная суперсила SolidStart — функции, которые всегда выполняются на сервере:
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"). Всё что внутри функции с этой директивой никогда не попадёт в клиентский бандл.
🌐 API Routes
Заголовок раздела «🌐 API Routes»// src/routes/api/users.ts → GET /api/usersimport { 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/:idexport 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].tsxexport 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> );}🌊 Streaming SSR
Заголовок раздела «🌊 Streaming SSR»// 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 (Vinxi)
Заголовок раздела «⚙️ app.config.ts (Vinxi)»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');