@putnami/web
v0.3.0
Published
React integration with SSR support, client-side hydration, and routing utilities
Maintainers
Readme
@putnami/web
React integration with SSR support, client-side hydration, and routing utilities built on React Router.
Installation
putnami deps add @putnami/webFeatures
- Server-Side Rendering (SSR) - React 19 HTML rendering with a streamed response body
- File-based Routing - Automatic route generation from file structure (convention over configuration)
- Type-safe Data Loading - Loaders and actions with full TypeScript support
- Client-side Hydration - Seamless hydration with React Router v7
- Conditional Exports - Automatic server/browser code splitting
- Document Management - Built-in helpers for SEO, meta tags, and document head
- Error Boundaries - Route-level error handling with error boundaries
- Form Handling - Progressive enhancement with type-safe form actions
- Prefetching - Intelligent link prefetching for better performance
Table of Contents
Quick Start
Using the React Plugin (Recommended)
The easiest way to get started is using the file-based routing plugin:
// src/main.ts
import { application } from '@putnami/application';
import { react } from '@putnami/web';
export const app = () => application().use(react());Then create your pages using the file-based routing convention:
// src/app/page.tsx
import { useLoaderData } from '@putnami/web';
interface PageData {
user: { name: string };
}
export default function HomePage() {
const { user } = useLoaderData<PageData>();
return <h1>Welcome, {user.name}!</h1>;
}
// src/app/loader.ts
import { loader } from '@putnami/web';
export default loader(async (ctx) => {
const user = await fetchUser(ctx);
return { user };
});Manual Route Configuration
For more control, you can manually configure routes:
import { ReactApplication } from '@putnami/web';
import { application, http } from '@putnami/application';
const reactApp = new ReactApplication();
// Add a page with loader
reactApp.reactPage('/', {
page: HomePage,
loader: homeLoader,
});
// Add layouts for nested routes
reactApp.reactLayout('/dashboard', DashboardLayout);
reactApp.reactPage('/dashboard/settings', {
page: SettingsPage,
loader: settingsLoader,
action: settingsAction,
});
// Register hydration script
reactApp.reactScript('/hydrate.main.js', 'hydrate.main.js');
// Integrate with main application
const app = application()
.use(http({ port: 3000 }))
.use(reactApp.getHttpPlugin());
app.start();📚 Learn More: See Getting Started Guide for a complete walkthrough.
Architecture
flowchart TD
A[HTTP Request] --> B[React Router Static Handler]
B --> C[Execute Loaders]
C --> D[Render React Components]
D --> E[Stream HTML Response]
E --> F[Client Receives HTML]
F --> G[Hydrate React App]
G --> H[Interactive Client App]
I[File Structure] --> J[React Plugin Scanner]
J --> K[Generate Routes]
K --> L[Build Client Bundle]
L --> M[Hydration Script]Request Flow
- Server-Side Rendering: When a request arrives, the server executes loaders, renders React components, and streams HTML
- Hydration: The client receives the HTML and hydrates it with React, making it interactive
- Client Navigation: Subsequent navigations happen client-side with data prefetching
Key Concepts
- Loaders: Server-side data fetching functions that run before rendering
- Actions: Server-side form handlers for mutations
- Layouts: Wrapper components that nest and share UI across routes
- Error Boundaries: Route-level error handling components
📚 Learn More: See Architecture Deep Dive for detailed information.
Documentation
Comprehensive guides to help you build great React applications:
- Getting Started - Installation, setup, and your first app
- Migration Guide - Migrating from React v1 to v2
- File-based Routing - Complete routing guide with patterns and conventions
- Data Loading - Loaders, actions, and type-safe data fetching
- Forms & Actions - Form submission patterns and validation
- Client-side Features - Hydration, hooks, and client patterns
- Document Management - SEO, meta tags, and document helpers
- Rendering Modes - SSR, SSG, ISR, islands, and the manifest
- Configuration - Complete configuration reference
- Advanced Patterns - Optimization, testing, and best practices
- API Reference - Complete API documentation
- Troubleshooting - Common issues and solutions
API Reference
ReactApplication
Main class for configuring React SSR routes.
| Method | Description |
|--------|-------------|
| reactPage(route, options) | Add a page with optional loader/action |
| reactLayout(route, layout, loader?) | Add a layout component |
| reactError(route, error) | Add an error boundary |
| reactScript(route, path) | Register a static script |
| getHttpPlugin() | Get the internal HttpPlugin |
Hooks
Type-safe wrappers around React Router hooks with improved generics.
useLoaderData<T>()
Returns typed loader data for the current route.
interface UserData {
name: string;
email: string;
}
function Profile() {
const data = useLoaderData<UserData>();
return <span>{data.name}</span>;
}useActionData<T>()
Returns action data with ok and status fields, or undefined if no action has been performed.
interface FormResult {
message: string;
}
function Form() {
const result = useActionData<FormResult>();
if (result?.ok) {
return <div>{result.message}</div>;
}
return <form>...</form>;
}useRouteLoaderData<T>(id)
Returns loader data for a specific route by ID.
function ChildComponent() {
const rootData = useRouteLoaderData<RootData>('root');
return <span>{rootData.user.name}</span>;
}Other Re-exported Hooks
All standard React Router hooks are re-exported:
useNavigation- Navigation stateuseNavigate- Programmatic navigationuseParams- Route parametersuseLocation- Current locationuseSearchParams- Query parametersuseFetcher- Data fetching without navigationuseSubmit- Form submission- And more...
Dependencies
This package relies on the following key dependencies:
- React (
^19.2.3) - React rendering and component model - React Router (
^7.14.2) - Client-side routing and SSR support - React DOM (
^19.2.3) - DOM rendering and hydration
Conditional Exports
The package uses conditional exports for automatic code splitting:
{
"exports": {
".": {
"browser": "./src/index.browser.ts",
"default": "./src/index.ts"
}
}
}- Default/server graph: SSR, generation, loaders, actions, and client-compatible re-exports
- Browser graph: Client components, hydration, hooks, and lightweight route builders
The two entries are published from separate build graphs. Browser code cannot reach SSR implementations or server-only runtime imports through a shared chunk. Loaders, actions, and authorization always execute on the server; the browser receives only serialized loader/action results and bounded authentication/role/scope hints for conditional UI.
Examples
Simple Blog
// src/app/posts/[slug]/page.tsx
import { useLoaderData } from '@putnami/web';
export default function PostPage() {
const { post } = useLoaderData<{ post: Post }>();
return <article>{post.content}</article>;
}
// src/app/posts/[slug]/loader.ts
import { loader } from '@putnami/web';
export default loader(async (ctx) => {
const slug = ctx.params.slug;
const post = await getPost(slug);
return { post };
});Form with Action
// src/app/contact/page.tsx
import { Form, useActionData } from '@putnami/web';
export default function ContactPage() {
const result = useActionData<{ message: string }>();
return (
<Form method="post">
<input name="email" type="email" />
<button type="submit">Send</button>
{result?.ok && <p>{result.message}</p>}
</Form>
);
}
// src/app/contact/action.ts
import { action } from '@putnami/web';
export default action(async (ctx) => {
const formData = await ctx.req.formData();
await sendEmail(formData.get('email'));
return { message: 'Email sent!' };
});📚 More Examples: See Advanced Patterns for real-world examples.
Troubleshooting
Common issues and quick fixes:
- Hydration mismatch: Ensure server and client render the same HTML
- Route not found: Check file naming conventions match expected patterns
- Loader errors: Verify the loader resolves serializable data or an explicit response
- Action not working: Ensure form has
method="post"and action is defined
📚 Full Guide: See Troubleshooting Guide for detailed solutions.
Support and contract
@putnami/web is a public, documented, maintained package classified stable.
The web-application-delivery specification
and the accepted decisions for
separate render graphs
and server-authoritative data/security
define the promise.
The contract includes explicit SSR/SSG/ISR/island modes, request-isolated
document state, script-safe hydration data, validated server loaders/actions,
layout/page security propagation, default CSP/CSRF protection, cache isolation,
and a deterministic render manifest. OAuth identity, API streaming, UI styling,
and persistence remain owned by their framework packages. No
default-framework or cross-language parity claim is made. Before v1.0.0, minor
0.x releases may contain documented breaking changes.
