• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
bitsoflogic

bitsoflogic

vtrr

Val Town React Router fullstack framework
Remix of stevekrouse/vtrr
Unlisted
Like
vtrr
Home
Code
8
demos
2
README.md
client-runtime.ts
jsx-runtime
mod.ts
routes.ts
server.ts
types.ts
Environment variables
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
…
Viewing readonly version of main branch: v5
View latest version
README.md

vtrr — Val Town React Router

A zero-boilerplate fullstack React framework for Val Town. Server-side render React apps with file-based loaders and actions, client-side hydration, and single-fetch navigation — all in a few lines of code.

How It Works

Rendering mermaid diagram...

Quick Start

Create an HTTP val with a single file — components, loader, action, and routes all in one:

Create val
/** @jsxImportSource https://esm.sh/react@18.2.0 */ import { useLoaderData } from "https://esm.sh/react-router@7.5.0?deps=react@18.2.0,react-dom@18.2.0"; import { defineRoutes } from "https://esm.town/v/stevekrouse/vtrr/routes.ts"; import { createApp } from "https://esm.town/v/stevekrouse/vtrr/server.ts"; function Home() { const { time } = useLoaderData() as { time: string }; return <h1>Server time: {time}</h1>; } export const loader = () => ({ time: new Date().toLocaleString() }); export const routes = defineRoutes([ { path: "/", Component: Home, loader: import.meta.url }, ]); export default createApp({ routes });

That's it. SSR, hydration, and client-side navigation — all wired up automatically.

Architecture

Rendering mermaid diagram...

Core Files

FilePurpose
mod.tsPublic entry point — re-exports createApp, defineRoutes, and types
server.tsHono server setup, SSR rendering, static file serving, hydration data injection
routes.tsdefineRoutes() — transforms user route configs into React Router route objects (server: real imports, client: stubs)
client-runtime.tsBrowser hydration script with single-fetch dataStrategy for client-side navigation
types.tsTypeScript interfaces (AppOptions, UserRouteConfig, re-exported RR types)

API

createApp(options: AppOptions)

Creates a (req: Request) => Promise<Response> handler. Export it as default from an HTTP val.

Create val
export default createApp({ routes, // from defineRoutes() head: '<title>My App</title>', // extra <head> content rootId: 'root', // DOM element ID (default: "root") routesFile: './routes.tsx', // optional: explicit routes file path setup: (app) => { /* add Hono middleware/API routes */ }, handler: (defaultHandler) => async (req) => { /* wrap the handler */ }, });

defineRoutes(configs: UserRouteConfig[])

Converts your route config into React Router-compatible route objects. Loaders and actions are specified as string paths (resolved to dynamic imports on the server, stubbed on the client).

Create val
export const routes = defineRoutes([ { path: "/", Component: App, children: [ { index: true, Component: Home, loader: "./routes/Home.loader.ts", // string path! }, { path: "posts/:id", Component: Post, loader: "./routes/Post.loader.ts", action: "./routes/Post.action.ts", }, ], }, ]);

Loader/action resolution:

  • String paths (e.g. "./routes/Home.loader.ts") — resolved relative to VALTOWN_ENTRYPOINT on the server; stubbed on the client
  • import.meta.url — use when the loader is exported from the same file (great for single-file apps)

Route Config Shape

Create val
interface UserRouteConfig { path?: string; // URL pattern index?: boolean; // Index route flag Component?: React.ComponentType; // React component loader?: string; // Path to loader module action?: string; // Path to action module children?: UserRouteConfig[]; // Nested routes errorElement?: React.ReactElement; // Error boundary }

Single-Fetch Data Strategy

Client-side navigations use a single HTTP request per navigation instead of one request per loader. The client sends X-Data-Request: true header → the server runs all matched loaders → returns all data as one JSON response.

Rendering mermaid diagram...

Demos

Hello World

📁 demos/hello-world.tsx — Single-file app with loader, action, and interactive counter.

Message Board

📁 demos/message-board/ — Full multi-route app demonstrating:

demos/message-board/
├── index.tsx              # Entry point, route definitions
├── types.ts               # Shared TypeScript types
├── database/
│   ├── migrations.ts      # SQLite schema setup
│   └── queries.ts         # Database query functions
├── components/
│   ├── LoadingSpinner.tsx
│   ├── MessageForm.tsx
│   ├── MessageList.tsx
│   └── SearchForm.tsx
└── routes/
    ├── App.tsx             # Root layout with <Outlet>
    ├── Home.tsx            # Topic list page
    ├── Home.loader.ts      # Loads topics from DB
    ├── Topic.tsx            # Topic detail + messages
    ├── Topic.loader.ts
    ├── Topic.action.ts      # Post new messages
    ├── TopicMessage.loader.ts
    ├── Topics.action.ts     # Create new topics
    ├── Search.tsx
    └── Search.loader.ts

Key Design Decisions

  • Hono for HTTP — lightweight, fast, and gives users an escape hatch via setup() for API routes and middleware
  • String-based loader/action paths — loaders/actions never ship to the client; only the server dynamically imports them
  • Single-fetch on navigation — one request per navigation instead of waterfall requests per loader
  • /__src/* file serving — project source files served under a prefix to avoid conflicts with page routes
  • Hydration via window.__staticRouterHydrationData — server injects loader data, client picks it up seamlessly
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.