Skip to content

Full-stack RSC Architecture

Use this architecture for complete web applications with a unified codebase, server rendering, and streaming.

  • Building a complete web application
  • Want unified frontend + backend codebase
  • Need SSR and streaming
  • Prefer React Server Components model
Terminal window
npx create-velox-app my-app --rsc
# or with authentication
npx create-velox-app my-app --rsc-auth

VeloxTS uses Vinxi as the HTTP handler, with Fastify embedded for API routes:

Browser Request
Vinxi
├── /api/* → Fastify (tRPC + REST)
├── /_build/* → Static Assets
└── /* → RSC Renderer
my-app/
├── app/
│ ├── pages/ # File-based routing
│ │ ├── index.tsx # /
│ │ ├── about.tsx # /about
│ │ └── users/
│ │ ├── index.tsx # /users
│ │ └── [id].tsx # /users/:id
│ ├── layouts/
│ │ └── root.tsx # Root layout
│ └── actions/ # Server actions
│ └── users.ts
├── api/
│ └── procedures/ # Backend procedures
├── app.config.ts # Vinxi configuration
└── package.json

Pages in app/pages/ map to routes:

FileRoute
index.tsx/
about.tsx/about
users/index.tsx/users
users/[id].tsx/users/:id
posts/[...slug].tsx/posts/* (catch-all)

Pages are Server Components by default:

app/pages/users/index.tsx
import { db } from '@/api/database';
export default async function UsersPage() {
const users = await db.user.findMany();
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}

Call backend procedures from server actions:

app/actions/users.ts
'use server';
import { validated } from '@veloxts/web/server';
import { CreateUserSchema } from '@/api/schemas/user';
export const createUser = validated(CreateUserSchema, async (input) => {
const user = await db.user.create({ data: input });
return { success: true, user };
});

Use in forms:

app/pages/users/new.tsx
import { createUser } from '@/app/actions/users';
export default function NewUserPage() {
return (
<form action={createUser}>
<input name="name" required />
<input name="email" type="email" required />
<button type="submit">Create User</button>
</form>
);
}

Wrap pages with layouts:

app/layouts/root.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>
<nav>...</nav>
<main>{children}</main>
</body>
</html>
);
}