Documentation Index
Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt
Use this file to discover all available pages before exploring further.
Install dependencies
Server-side rendering requires the component and html-template packages:All necessary packages are included in the main remix package. Create a render utility
Set up a helper function to render components to HTML streams:import type { RemixNode } from 'remix/component'
import { renderToStream } from 'remix/component/server'
export function render(node: RemixNode, init?: ResponseInit) {
let stream = renderToStream(node, {
onError(error) {
console.error('Render error:', error)
},
})
let headers = new Headers(init?.headers)
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'text/html; charset=UTF-8')
}
return new Response(stream, { ...init, headers })
}
The renderToStream function converts your components into a streaming HTML response, enabling faster time-to-first-byte. Create a document layout
Build a base HTML document component:import type { RemixNode } from 'remix/component'
import { css } from 'remix/component'
interface DocumentProps {
title?: string
children: RemixNode
}
export function Document({ title = 'My App', children }: DocumentProps) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<nav mix={[css({ padding: '1rem', background: '#f0f0f0' })]}>
<a href="/">Home</a>
{' | '}
<a href="/about">About</a>
{' | '}
<a href="/contact">Contact</a>
</nav>
<main mix={[css({ padding: '2rem', maxWidth: '1200px', margin: '0 auto' })]}>
{children}
</main>
</body>
</html>
)
}
The css function allows you to write inline styles with full TypeScript support. Create page components
Build reusable page components:import { css } from 'remix/component'
import { Document } from '../layout.tsx'
interface HomePageProps {
userName?: string
posts: Array<{ id: number; title: string; excerpt: string }>
}
export function HomePage({ userName, posts }: HomePageProps) {
return (
<Document title="Home">
<h1>Welcome {userName ? userName : 'Guest'}!</h1>
<section mix={[css({ marginTop: '2rem' })]}>
<h2>Recent Posts</h2>
<div mix={[css({ display: 'grid', gap: '1rem', marginTop: '1rem' })]}>
{posts.map(post => (
<article
key={post.id}
mix={[
css({
padding: '1.5rem',
border: '1px solid #ddd',
borderRadius: '8px',
}),
]}
>
<h3 mix={[css({ marginBottom: '0.5rem' })]}>
<a href={`/posts/${post.id}`}>{post.title}</a>
</h3>
<p mix={[css({ color: '#666' })]}>
{post.excerpt}
</p>
</article>
))}
</div>
</section>
</Document>
)
}
Create route handlers
Connect your components to routes:import { createRouter } from 'remix/fetch-router'
import { routes } from 'remix/fetch-router/routes'
import { render } from './utils/render.ts'
import { HomePage } from './pages/home.tsx'
import { Document } from './layout.tsx'
export let appRoutes = routes({
home: 'GET /',
about: 'GET /about',
posts: {
show: 'GET /posts/:id',
},
})
export let router = createRouter()
// Home page with data fetching
router.get(appRoutes.home, async () => {
let posts = [
{
id: 1,
title: 'Getting Started with Remix',
excerpt: 'Learn the basics of building web apps with Remix.',
},
{
id: 2,
title: 'Advanced Routing Patterns',
excerpt: 'Explore nested routes and dynamic segments.',
},
]
return render(
<HomePage userName="Alice" posts={posts} />
)
})
// About page
router.get(appRoutes.about, () => {
return render(
<Document title="About">
<h1>About Us</h1>
<p>We build amazing web applications with Remix.</p>
</Document>
)
})
// Post detail page
router.get(appRoutes.posts.show, ({ params }) => {
let post = {
id: Number(params.id),
title: 'Getting Started with Remix',
content: 'This is a detailed post about Remix...',
}
return render(
<Document title={post.title}>
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
<a href="/">Back to home</a>
</article>
</Document>
)
})
Add CSS styling
Use the css helper for scoped, type-safe styles:import { css } from 'remix/component'
function Button({ children, primary }: { children: string; primary?: boolean }) {
return (
<button
mix={[
css({
padding: '0.5rem 1rem',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '1rem',
}),
primary && css({
background: '#0070f3',
color: 'white',
}),
!primary && css({
background: '#f0f0f0',
color: '#333',
}),
]}
>
{children}
</button>
)
}
The mix prop combines multiple style objects. Falsy values are ignored, making conditional styles easy. Add interactive features with Frames
Use Frames to embed dynamic, auto-updating content:import { Frame } from 'remix/component'
import { Document } from '../layout.tsx'
export function DashboardPage() {
return (
<Document title="Dashboard">
<h1>Dashboard</h1>
<div>
<h2>Live Stats</h2>
{/* This frame auto-refreshes every 5 seconds */}
<Frame src="/api/stats" />
</div>
<div>
<h2>Recent Activity</h2>
<Frame src="/api/activity" />
</div>
</Document>
)
}
Create the frame endpoints:import { css } from 'remix/component'
router.get('/api/stats', async () => {
let stats = {
visitors: Math.floor(Math.random() * 1000),
sales: Math.floor(Math.random() * 100),
}
return render(
<div mix={[css({ padding: '1rem', background: '#f9f9f9', borderRadius: '8px' })]}>
<p>Visitors: {stats.visitors}</p>
<p>Sales: {stats.sales}</p>
</div>
)
})
Optimize with streaming
The renderToStream function automatically streams HTML to the browser as it’s generated. For even faster initial page loads, stream expensive data:router.get(appRoutes.home, async () => {
// Start rendering immediately
let postsPromise = fetchPosts() // Don't await
return render(
<Document>
<h1>Welcome!</h1>
{/* This will be streamed when ready */}
<Suspense fallback={<p>Loading posts...</p>}>
{postsPromise.then(posts => (
<PostList posts={posts} />
))}
</Suspense>
</Document>
)
})
Component Best Practices
import type { RemixNode } from 'remix/component'
import { css } from 'remix/component'
interface CardProps {
title: string
children: RemixNode
}
export function Card({ title, children }: CardProps) {
return (
<div
mix={[
css({
padding: '1.5rem',
border: '1px solid #ddd',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
}),
]}
>
<h3 mix={[css({ marginBottom: '1rem' })]}>
{title}
</h3>
{children}
</div>
)
}
Type your props
Always use TypeScript interfaces for component props to get autocomplete and type checking.
Use semantic HTML
Use proper HTML elements (<article>, <section>, <nav>) for better accessibility and SEO.