Remix.js — fullstack React
Nested routes, loader/action, defer() streaming, useFetcher, optimistic UI, cookie sessions i progressive enhancement.
6 kluczowych konceptów Remix
Nested Routes, Loader, Action, defer, useFetcher i ErrorBoundary — opis i kiedy stosować.
| Koncept | Opis | Kiedy |
|---|---|---|
| Nested Routes | Layouty zagnieżdżone | Blog, dashboard — wielopoziomowe layouty |
| Loader | Serwer data fetching | GET data — DB query, API, session check |
| Action | Serwer mutacja | POST/PUT/DELETE — forma submit, delete |
| defer() + Await | Streaming SSR | Wolne dane — stream zamiast czekać |
| useFetcher | Fetch bez nawigacji | Like button, autocomplete, optymistyczny UI |
| ErrorBoundary | Per-route error handling | Izolacja błędów — nie crash całej strony |
Często zadawane pytania
Co to jest Remix i jak działają nested routes?
Remix: fullstack React framework. Progressive enhancement — działa bez JavaScript. Web fundamentals (fetch, forms, HTTP). Vite pod spodem (Remix 2.x). Ryan Florence i Michael Jackson. Nested Routes: każda trasa może mieć layout. app/routes/_index.tsx — / (strona główna). app/routes/blog._index.tsx — /blog (lista). app/routes/blog.$slug.tsx — /blog/:slug. app/routes/blog.tsx — layout dla wszystkich /blog/*. Outlet: {Outlet} — gdzie renderować dzieci. blog.tsx renderuje {Outlet} — wpisy wchodzą tutaj. Nested loading: wszystkie loadery równolegle. Parent i child loady równocześnie. Szybsze niż waterfall Next.js Pages Router. Layouty: każda trasa ma swój ErrorBoundary. app/root.tsx — root layout. HydrateFallback — loading state podczas nawigacji. Route konwencje: blog._index (index trasy). blog.$slug (parametr). blog.new (statyczna). _layout.blog (grupowanie bez URL). Splat routes: $.tsx — catch-all. Params: params.slug w loader. Resource routes: app/routes/api.users.ts — tylko loader/action (bez domyślnego eksportu komponentu). PDF download, JSON API, webhooks. Remix vs Next.js App Router: Remix — prostszy model. Next.js — RSC/Server Components. Remix — progressive enhancement z natury. Next.js — większy ekosystem.
Loader i Action — data fetching i mutacje w Remix?
Loader: export async function loader({params, request}: LoaderFunctionArgs) { const url = new URL(request.url). const q = url.searchParams.get('q'). const posts = await db.post.findMany({where: {title: {contains: q}}}). return json({posts, q}) }. Dostęp w komponencie: const {posts, q} = useLoaderData(). Type-safe: InferredLoaderData. TypeErrors przy zmianie zwracanego kształtu. Request: URL, headers, cookies, body. Throw redirect: if (!session.user) throw redirect('/login'). Response: json(data) — JSON z headers. redirect(url) — przekierowanie. Streaming: defer({}: LoaderFunctionArgs) { const fastData = await getFast(). const slowPromise = getSlow(). return defer({fastData, slowPromise}) }. Await komponent: Await resolve={slowPromise} errorElement={Error}. {(data) => Component}. Await — stream data do klienta. Action: export async function action({request}: ActionFunctionArgs) { const formData = await request.formData(). const title = formData.get('title') as string. const post = await db.post.create({data: {title, authorId: session.userId}}). return redirect('/blog/' + post.slug) }. Form: Form method='post' action='/blog/new'. Działa bez JS! Validation: const errors = validate(formData). if (errors) return json({errors}, {status: 400}). useActionData() — pobierz errors. useNavigation: navigation.state — idle, loading, submitting. Pending UI bez useState.
Optimistic UI, fetcher i parallel data loading?
useFetcher: fetch bez nawigacji. const fetcher = useFetcher(). fetcher.submit(formData, {method: 'post', action: '/api/like'}). fetcher.data — wynik. fetcher.state — loading state. Optimistic UI z fetcher: const isLiked = fetcher.formData ? fetcher.formData.get('action') === 'like' : post.liked. button className={isLiked ? 'liked' : ''} onClick. Natychmiast aktualizuj UI — nie czekaj na serwer. Rollback przy błędzie automatyczny. useOptimistic (React 19): const [optimisticLikes, addOptimisticLike] = useOptimistic(likes, (state, amount) => state + amount). Parallel loaders: Remix ładuje wszystkie nested loaders równolegle. Parent route loader + child route loader jednocześnie. Dramatyczna poprawa vs waterfall. Cache-Control headers: export function headers() { return {'Cache-Control': 'max-age=300'} }. CDN caching dla static content. Stale-while-revalidate. shouldRevalidate: export function shouldRevalidate({currentUrl, nextUrl, formMethod}) { if (formMethod === 'GET') return false. return defaultShouldRevalidate }. Nie przeładowuj niepotrzebnie. clientLoader (Remix v2.9+): export async function clientLoader({serverLoader}) { const serverData = await serverLoader(). return {...serverData, clientData: localStorage.getItem('...')} }. Łącz serwer + klient data. clientAction: akcje po stronie klienta. Error handling: ErrorBoundary per route. useRouteError() — sprawdź typ błędu. isRouteErrorResponse(error) — HTTP error. Granularne błędy — nie cała strona.
Authentication, sessions i cookies w Remix?
Cookie Session Storage: import {createCookieSessionStorage} from '@remix-run/node'. const sessionStorage = createCookieSessionStorage({cookie: {name: '__session', httpOnly: true, secure: true, sameSite: 'lax', secrets: [process.env.SESSION_SECRET]}}). export const {getSession, commitSession, destroySession} = sessionStorage. Login loader: export async function action({request}: ActionFunctionArgs) { const session = await getSession(request.headers.get('Cookie')). const {email, password} = Object.fromEntries(await request.formData()). const user = await authenticate(email, password). if (!user) return json({error: 'Invalid'}, {status: 401}). session.set('userId', user.id). return redirect('/dashboard', {headers: {'Set-Cookie': await commitSession(session)}}) }. Protected route loader: const session = await getSession(request.headers.get('Cookie')). const userId = session.get('userId'). if (!userId) throw redirect('/login'). Remix Auth (sergiodxa): npm install remix-auth. FormStrategy, GoogleStrategy, etc. Authenticator z TypeScript. Clerk integration: ClerkApp wrapper. rootAuthLoader. getAuth(). Clerk + Remix pełna integracja. Database sessions: createDatabaseSessionStorage. CSRF: csrf package. Remix forms — SameSite cookie protection wbudowane. Rate limiting: upstash-ratelimit w loader. X-Forwarded-For IP. Remix + Prisma: createPrismaSessionStorage. Lub własne queries. Flash messages: session.flash('error', 'Something went wrong'). session.get('error') — odczyt i usunięcie.
Deployment i produkcyjne wzorce Remix — Vercel, Fly.io i Cloudflare?
Adaptery Remix: @remix-run/node — Express, Fastify, server. @remix-run/cloudflare — Cloudflare Workers. @remix-run/architect — AWS Lambda. Vercel: @vercel/remix. vercel.json z rewrites. Edge runtime opcjonalnie. SSR per request. Fly.io: Remix app template. Dockerfile included. Global edge deployment. Persistentny serwer — nie serverless. Baza danych blisko servera. Cloudflare Pages + Workers: export default {fetch: createPagesFunctionHandler(build)}. D1 (SQLite), KV, R2 dostępne. Globalne edge — ultra niska latencja. Limitacje: 5MB bundle size, czas CPU. Struktura projektu: app/root.tsx — root layout, global error, meta. app/routes/ — trasy. app/components/ — UI komponenty. app/lib/ — utilities, DB clients. app/styles/ — CSS. app/entry.client.tsx — klient hydration. app/entry.server.tsx — server rendering. Progressive Enhancement: Działa bez JS: Form submit. Navigation. Cookies. Fallback dla JS-disabled. Enhanced z JS: useFetcher. useNavigation pending. Optimistic UI. SPA transitions. Testing Remix: @testing-library/react z MemoryRouter. Playwright dla E2E (routing, forms). Mock loader/action przez jest.mock. Integration test z supertest. Meta i headers funkcje: export function meta({data, params}: MetaFunction). Open Graph, Twitter Cards. Dynamic SEO. Remix i Vite (2.x): HMR szybki. Plugin @remix-run/dev. Zrównanie z ekosystemem Vite.
Powiązane artykuły
Skontaktuj się z nami
Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.
Wyślij zapytanie
Telefon
+48 790 814 814
Pon-Pt: 9:00 - 18:00
adam@fotz.pl
Odpowiadamy w ciągu 24h
Adres
Plac Wolności 16
61-739 Poznań
Godziny pracy
Wolisz porozmawiać?
Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.
Zadzwoń teraz