@roostjs/start Guides
Task-oriented instructions for routing, server functions, and SSR with TanStack Start.
How to create a new route
Routes in TanStack Start are file-based. Create a file under src/routes/ and export a Route using createFileRoute.
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/posts/$id')({
component: PostPage,
});
function PostPage() {
const { id } = Route.useParams();
return <div>Post: {id}</div>;
}For API endpoints, use a loader or server function rather than returning HTML from the component. Nested routes inherit the parent layout automatically.
import { createFileRoute, Link } from '@tanstack/react-router';
export const Route = createFileRoute('/posts/')({
loader: async () => {
return await fetchPosts();
},
component: PostsPage,
});
function PostsPage() {
const posts = Route.useLoaderData();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link to="/posts/$id" params={{ id: post.id }}>{post.title}</Link>
</li>
))}
</ul>
);
}How to use server functions
Use roostFn for server functions that read data, and roostFnWithInput when the function accepts typed user input.
import { roostFn, roostFnWithInput } from '@roostjs/start';
import { roostMiddleware } from '../middleware';
// No input — read-only server function
export const listPosts = roostFn(roostMiddleware, async (roost) => {
const postService = roost.container.resolve(PostService);
return postService.findAll();
});
// Typed input — mutation server function
export const createPost = roostFnWithInput(
roostMiddleware,
(d: { title: string; body: string }) => d,
async (roost, input) => {
const postService = roost.container.resolve(PostService);
return postService.create(input);
}
);import { createFileRoute } from '@tanstack/react-router';
import { createPost } from '../../functions/posts';
export const Route = createFileRoute('/posts/new')({ component: NewPostPage });
function NewPostPage() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
await createPost({ title: form.get('title') as string, body: form.get('body') as string });
};
return (
<form onSubmit={handleSubmit}>
<input name="title" placeholder="Title" />
<textarea name="body" placeholder="Body" />
<button type="submit">Publish</button>
</form>
);
}How to access the Roost container from routes
Use createRoostMiddleware to bootstrap your application and expose the container via the roost context in server functions and loaders.
import { createRoostMiddleware } from '@roostjs/start';
import { Application } from '@roostjs/core';
import { CloudflareServiceProvider } from '@roostjs/cloudflare';
import { AuthServiceProvider } from '@roostjs/auth';
export const roostMiddleware = createRoostMiddleware(() => {
const app = new Application({});
app.register(CloudflareServiceProvider);
app.register(AuthServiceProvider);
return app;
});import { roostFn } from '@roostjs/start';
import { roostMiddleware } from '../middleware';
export const getCurrentUser = roostFn(roostMiddleware, async (roost) => {
// roost.container gives you the fully-booted DI container
const userService = roost.container.resolve(UserService);
return userService.getCurrentUser(roost.request);
});How to configure SSR
SSR is enabled by default in TanStack Start. Configure it in app.config.ts. For streaming SSR, set renderMode to 'stream'.
import { defineConfig } from '@tanstack/start/config';
export default defineConfig({
server: {
preset: 'cloudflare-pages',
},
routers: {
ssr: {
entry: './src/entry.server.tsx',
},
client: {
entry: './src/entry.client.tsx',
},
},
});To opt a route out of SSR and render it client-only, use the clientOnly loader modifier from TanStack Start. See TanStack Start SSR docs for streaming and hydration options.