unirend
v0.0.24
Published
A lightweight toolkit for working with both SSG (Static Site Generation) and SSR (Server-Side Rendering) in your Vite + React projects.
Readme
Unirend v0.0.24
Unirend is a lightweight SSR (Server-Side Rendering), SSG (Static Site Generation), and server toolkit for Vite + React Router projects. The name is a blend of “unified” and “render,” reflecting its goal to unify your build-time and runtime rendering workflows in a single, clean API.
Unirend helps you ship SEO-friendly pages and accurate social sharing previews by rendering content at build-time or server-time where needed. You can take a standard Vite + React Router project and, by changing a few files, convert it into an SSG or SSR project with minimal configuration.
The focus is on small, focused building blocks rather than a heavyweight, all-in-one framework. Unirend keeps routing in React Router, builds on Vite, and gives you explicit server utilities for API routes, page data loaders, plugins, uploads, redirects, static serving, and production runtime behavior when your app needs them.
⚠️ Note: This package is currently in active development and not yet ready for production use.
Installation
npm install unirend
# or
bun add unirend
# or
yarn add unirendPeer Dependencies: You'll also need to install these in your project:
lifecycleion is a utility library providing lifecycle management, structured logging, retry logic, and other foundational utilities. It is used internally by Unirend, in generated project templates, and is useful in your own server and application code as well.
npm install lifecycleion react react-dom react-router
npm install --save-dev vite
# or
bun add lifecycleion react react-dom react-router
bun add -d vite
# or
yarn add lifecycleion react react-dom react-router
yarn add --dev viteYou'll also need @vitejs/plugin-react as a dev dependency for your Vite config. Unirend does not depend on it directly, but every project needs it to configure React support in Vite:
npm install --save-dev @vitejs/plugin-react
# or
bun add -d @vitejs/plugin-react
# or
yarn add --dev @vitejs/plugin-reactUnirend includes Fastify as a regular dependency powering its built-in servers (SSR, API, redirect, and static file serving), so you don't need to install it separately.
Runtime Requirements
- Node >= 25 (uses newer web APIs such as
fetch,structuredClone, andAbortSignal.timeout, covers the Node 20.19.0 minimum required by Vite 8 forrequire(esm)support without a flag, and also meets the Node 25 requirement of thelifecycleionpeer dependency, which relies on browser-style global error event APIs such asErrorEventandreportError) - Or Bun with equivalent APIs
Toolchain Recommendation
We recommend Bun as the default toolchain, specifically for its bundler, running helper scripts during development, and as a unit test runner. For production runtime stability, we recommend running the bundle under Node. Pass --target node when building with Bun (see the optional scripts in the SSR section below). Pure Node tooling setups (e.g., ts-node, tsc, esbuild, rollup) or vanilla JavaScript are possible, but not the focus of this guide, the CLI, or the starter template utility functions. This split is a pattern others have landed on as well. The Cloudflare Wrangler team, for example, recommends using Bun as the package manager but running under Node.
Note: Always include --external vite when bundling your server entry with bun build. Vite lazily imports esbuild at runtime, which Bun's bundler cannot statically resolve. Keeping Vite external avoids a build error.
Note: Running the SSR dev server under Bun may stall graceful shutdown. The Vite HMR WebSocket server can fail to close cleanly under Bun, compared to Node. The same style of issue is described in docs/websockets.md, along with the Node-based workaround covered in docs/ssr.md.
CLI note: The Unirend project generator (CLI) requires Bun for a simple, out‑of‑the‑box experience. Generated projects use Bun for development and build tooling, and target Node by default at bundle time (bun build --target node --external vite). As Node tooling continues to improve, we may add first-class Node CLI support in the future.
Repo auto‑init: The CLI sets up a repository structure that supports multiple projects in one workspace. You can initialize it explicitly with init-repo, but if it’s missing when you run create, Unirend will set it up automatically with a sensible default.
- Installation
- Common Setup for SSG (Static Site Generation) or SSR (Server-Side Rendering)
- Public App Config Pattern
- SSG (Static Site Generation)
- SSR (Server-Side Rendering)
- Demos
- Data Loaders
- API Envelope Structure
- Error Handling
- File Upload Helpers
- UX Suggestions
- Development
- Build Info Utilities
- Utilities
Common Setup for SSG (Static Site Generation) or SSR (Server-Side Rendering)
Between both SSG (Static Site Generation) and SSR (Server-Side Rendering), there is some overlapping setup.
Prepare Client Frontend
- Create a Vite + React project, like normal. Define your routes using React Router's
RouteObject[]format. - Rename your module in the
index.htmlfile to something likeEntryClientand update the reference. - In your client entry point, use
mountAppinstead ofcreateRoot, passing your routes directly:
// EntryClient.tsx
import { mountApp } from 'unirend/client';
import { routes } from './Routes';
// Pass routes directly - mountApp handles creating the router
mountApp('root', routes, {
strictMode: true,
// Optional: wrap the entire app above the router with root-level providers
// rootProviders: ({ children }) => <ThemeProvider>{children}</ThemeProvider>
});- Important: Add SSR/SSG comment markers to your
index.htmltemplate:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Your App</title>
<!--ss-head-->
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"><!--ss-outlet--></div>
<script type="module" src="/src/EntryClient.tsx"></script>
</body>
</html><!--ss-head-->: Marks where server/SSG-rendered head content will be injected<!--ss-outlet-->: Marks where server/SSG-rendered body content will be injected- These comments are preserved during processing and are required for SSR/SSG to work properly
Managing <title>, <meta>, and <link> tags: Use UnirendHead from unirend/client, Unirend's built-in document head manager. It works identically in SSR, SSG, and SPA mode and injects into the <!--ss-head--> slot on the server.
import { UnirendHead } from 'unirend/client';
function HomePage() {
return (
<>
<UnirendHead>
<title>Home - My App</title>
<meta name="description" content="Welcome to my app" />
<meta property="og:title" content="Home - My App" />
<link rel="canonical" href="https://example.com/" />
</UnirendHead>
<main>...</main>
</>
);
}See docs/unirendhead.md for full API details.
For more details on mounting, options, and the rootProviders option, see docs/mount-app-helper.md.
Note on React Router Import:
- React Router is a peer dependency and required.
- Unirend targets React Router v7+ where browser APIs are provided by
react-router. Usereact-routerconsistently for imports (e.g.,Link,NavLink,useLocation). Do not mix withreact-router-domin the same codebase. - If your scaffold or AI template used
react-router-dom, search/replace those imports toreact-routeras part of preparation. - Do not create your own browser router in the client. Export
routes: RouteObject[]and letmountApphandlecreateBrowserRouter(routes)and hydration.
Prepare Vite Config and Entry Points
Vite Configuration: In your vite.config.ts, wrap your Vite config with withUnirendViteConfig() so dev/build flows use one React/React Router instance:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { withUnirendViteConfig } from 'unirend/config-vite';
export default defineConfig(
withUnirendViteConfig({
plugins: [react()],
build: {
manifest: true, // Required for unirend to locate built files
},
}),
);withUnirendViteConfig() merges with your existing Vite config, so settings like resolve.alias and ssr.noExternal are preserved. It configures Vite to avoid externalizing unirend during SSR and dedupes react, react-dom, and react-router so SSR/SSG rendering uses the same React/React Router package instances. This prevents split React Router contexts. Without it, router hooks like useLocation() can fail because they read a different context than the provider created.
Build Structure: Both SSG and SSR require building client and server separately:
- Client build: Contains static assets, client-side code, regular manifest, and SSR manifest (intended for pre-loading)
- Server build: Contains the server-side rendering entry point and server manifest
Note: Use different output directories for client and server (e.g., build/client and build/server). Reusing the same output directory for both can cause files to overwrite each other.
SSG note: Static Site Generation invokes your server entry at build time to render HTML files, but the generated output is served by a static file server or CDN at runtime. The build-time server is not your production runtime server.
Choose Your Rendering Strategy
1. Create Server Entry Point
Create a server entry file that exports a render function:
- For SSG: Create
EntrySSG.tsx - For SSR: Create
EntrySSR.tsx
import { unirendBaseRender } from 'unirend/server';
import type { RenderRequest } from 'unirend/server';
import { routes } from './Routes';
export async function render(renderRequest: RenderRequest) {
// Pass routes directly - unirendBaseRender handles the rest
return await unirendBaseRender(renderRequest, routes, {
strictMode: true,
// Optional: Add custom wrappers for additional providers
// rootProviders: ({ children }) => <StateProvider>{children}</StateProvider>
});
}For more details on the base render helper and options, see docs/base-render.md.
2. Build Commands
# Build client (contains static assets, regular manifest, and SSR manifest)
vite build --outDir build/client --base=/ --ssrManifest
# Build server entry (contains the rendering code)
# For SSG:
vite build --outDir build/server --ssr src/EntrySSG.tsx
# For SSR:
vite build --outDir build/server --ssr src/EntrySSR.tsx3. package.json Scripts
Add these scripts to your package.json for both SSG and SSR workflows. The examples below are illustrative and your actual scripts may vary. If you use the CLI to generate your project, the scripts are wired up automatically.
{
"scripts": {
"spa-dev": "vite", // SPA-only dev mode: no SSR server, frontend calls to server-registered page data/API handlers will 404 because they do not exist on Vite's HMR dev server
"build:client": "vite build --outDir build/client --base=/ --ssrManifest",
// For SSG:
"ssg:build:server": "vite build --outDir build/server --ssr src/EntrySSG.tsx",
"ssg:build": "bun run build:client && bun run ssg:build:server",
"generate:dev": "bun run generate.ts dev", // Generate with dev mode enabled
"generate:prod": "bun run generate.ts prod", // Generate for production
"ssg:build-and-generate:prod": "bun run ssg:build && bun run generate:prod",
"serve:dev": "bun run serve.ts dev", // Static server with dev runtime (verbose errors)
"serve:prod": "bun run serve.ts prod", // Static server with prod runtime
"ssg:build-generate-serve:prod": "bun run ssg:build-and-generate:prod && bun run serve:prod",
// For SSR:
"ssr:build:server": "vite build --outDir build/server --ssr src/EntrySSR.tsx",
"ssr:build": "bun run build:client && bun run ssr:build:server",
"ssr:serve:hmr": "bun run serve-hmr.ts dev", // SSR HMR mode, serving source through Vite
"ssr:serve:prod": "bun run serve-built.ts prod", // SSR prod mode with built assets (requires ssr:build first)
"ssr:build-and-serve:prod": "bun run ssr:build && bun run ssr:serve:prod",
// For SSR Production Build (Node runtime — recommended):
"build:prod": "bun build serve-built.ts --outdir build/serve --target=node --external vite",
"start": "node build/serve/serve-built.js prod"
// Alternative: Run directly under Bun runtime:
// "build:prod": "bun build serve-built.ts --outdir build/serve --external vite",
// "start": "bun build/serve/serve-built.js prod"
}
}Tip: Always include --target node and --external vite when building for the Node runtime. To run under Bun instead, omit --target node and replace node with bun in the start script.
Note: If you prefer a pure-Node toolchain without Bun, explore compiling or bundling your server with tools like tsc, esbuild, rollup, or tsup, or use vanilla JavaScript, then run with node. These alternatives are not covered in depth here to keep the setup simple and easy out of the box.
Public App Config Pattern
You can provide safe-to-share app configuration via the publicAppConfig option. In SSR and SSG, Unirend injects this config into the frontend so components can read it with usePublicAppConfig(). This works in both SSG and SSR modes (both dev and prod) when using generateSSG, serveSSRWithHMR, or serveSSRBuilt.
In React Components - use the usePublicAppConfig() hook:
import { usePublicAppConfig } from 'unirend/client';
function MyComponent() {
const config = usePublicAppConfig();
// Access config values with fallbacks
const api_endpoint = (config?.api_endpoint as string) || "http://localhost:3001";
const environment = (config?.environment as string) || "development";
return <div>API: {api_endpoint}</div>;
}All four context hooks, usePublicAppConfig(), useRequestContext(), useCDNBaseURL(), and useDomainInfo(), work on both server and client. See Unirend Context for full hook documentation.
In Non-Component Code (loaders, utilities, module-level) - access window.__PUBLIC_APP_CONFIG__, window.__FRONTEND_REQUEST_CONTEXT__, window.__CDN_BASE_URL__, and window.__DOMAIN_INFO__ directly:
// Non-component code runs outside React component tree, so use direct window access. For example in a data loader.
// Note: these globals only exist in the browser — `window` is not available during SSR, so always
// guard with `typeof window !== 'undefined'` and provide a server-side fallback.
// Client: reads api_endpoint from publicAppConfig when set (e.g. when the API runs on
// a separate server). Falls back to window.location.origin for same-server setups —
// no config needed. Set api_endpoint in publicAppConfig to override.
//
// Server: uses INTERNAL_API_ENDPOINT when set — useful when running SSR and API in
// separate server pools where the internal hostname differs from the public URL.
// Falls back to a localhost URL as a best-effort default for the co-located case.
// In co-located setups the handler short-circuits on the same instance anyway, so
// the exact fallback URL rarely matters. Update the port to match your API server,
// or set INTERNAL_API_ENDPOINT to an explicit URL for separate-server deployments.
const APIBaseURL =
typeof window !== 'undefined'
? (window.__PUBLIC_APP_CONFIG__?.api_endpoint as string) ||
window.location.origin
: (process.env.INTERNAL_API_ENDPOINT ?? 'http://localhost:3000');
const config = createDefaultPageDataLoaderConfig(APIBaseURL);
export const homeLoader = createPageDataLoader(config, 'home');
// Similarly, request context values (e.g. set by SSR middleware or SSG page definitions)
// are available on the client via window.__FRONTEND_REQUEST_CONTEXT__ after hydration:
const theme =
typeof window !== 'undefined'
? (window.__FRONTEND_REQUEST_CONTEXT__?.theme as string | undefined)
: undefined;
// The CDN base URL is always injected by the framework (empty string when not configured):
const cdnBase = typeof window !== 'undefined' ? window.__CDN_BASE_URL__ : '';
// Domain info (hostname + rootDomain, useful for subdomain-spanning cookies) — SSR/SSG with hostname configured:
const domainInfo =
typeof window !== 'undefined' ? window.__DOMAIN_INFO__ : null;Note: If you run Vite in SPA-only dev mode directly (not through the SSR dev/prod servers), the injection won't happen. All four globals will be undefined, so use fallback values as shown above.
Note on timing: All four globals are injected into <head> by the server, before any of your app scripts (whether in <head> or <body>), so they are available everywhere, including inline <head> scripts, body scripts, and all module code that runs after page load.
For more details on the Unirend Context system, see docs/unirend-context.md.
SSG (Static Site Generation)
After completing the Common Setup, see the dedicated guide for Static Site Generation:
SSR (Server-Side Rendering)
After completing the Common Setup, see the dedicated guide for Server-Side Rendering:
SSR servers support a plugin system for extending functionality, data loader endpoints for page data handling, and can host your API endpoints for actions outside of SSR data loader handlers. You can also create a standalone API server covered in docs/ssr.md, which is useful when you want to separate API hosting from SSR rendering while sharing the same plugin and handler code conventions, such as API endpoints and data loaders.
Demos
Runnable, self-contained examples live under demos/ and are wired to root-level scripts (no need to cd):
demos/ssg: SSG example (build, generate, serve)demos/ssr: SSR example (dev and production)demos/multi-app-ssr: Multi-app SSR example (cookie-based app switching, two real apps + one intentional error case)demos/api-server-demo.ts: API-only server exampledemos/api-static-content-demo.ts: API server with static file serving and split HTML/JSON handlersdemos/ws-server-demo.ts: WebSocket server example (SSR + API servers)
Runtime note: Demo scripts use Bun to run TypeScript directly (e.g., bun run ...). You can use Node-based alternatives as well (e.g., transpile with tsc, use ts-node, or write equivalent vanilla JavaScript). Unirend’s SSG and server (SSR/API) APIs run on Node and Bun. Vite provides HMR in development and bundles the React application frontend for production.
SSG Demo: Build and Serve
Files live in demos/ssg.
From the repo root (using package scripts):
# Build client and server for SSG.
bun run ssg:build
# Generate static HTML files using the built server entry.
bun run ssg:generate:prod
# Or build and generate in one step.
bun run ssg:build-and-generate:prod
# Serve the generated site with production runtime behavior.
bun run ssg:serve:prod
# Or do everything (build, generate, serve) in one step.
bun run ssg:build-generate-serve:prodNotes:
generate.tscallsgenerateSSGwith a mix of SSG and SPA pages, injectspublicAppConfig, and passesrequestContext(used by the ThemeProvider) per page.src/components/AppLayout.tsxowns the shared route chrome and route-change scroll-to-top behavior. Individual pages keep only their own page content and metadata.demos/ssg/serve.tsserves the contents ofbuild/clientusingStaticWebServerwrapped in aLifecycleManager+BaseComponentfor graceful shutdown and signal handling.- See docs/ssg.md for concepts behind the workflow.
SSR Demo: Dev and Prod
Files live in demos/ssr.
From the repo root (using package scripts):
# Development SSR with Vite HMR + source entry.
bun run ssr:serve:hmr
# Production: build client and server, then run the built server.
bun run ssr:build
bun run ssr:serve:prod
# Or do both in one step.
bun run ssr:build-and-serve:prodWhat this shows:
- Registering SSR plugins (cookies, theme, API routes, hooks).
- Server theme plugin seeding
requestContext.themePreferencefrom a cookie for flash-free dark/light mode. - API/SSR coexistence: API routes under
/api/*are handled first. Unmatched ones return JSON envelopes. Other GETs fall through to SSR. - Custom standalone 500 page handling via
get500ErrorPageinserver/ssr-component.ts. LifecycleManager+BaseComponentfor graceful shutdown with configurable timeouts (serve-hmr.ts/serve-built.ts→server/start.ts→server/ssr-component.ts).src/components/AppLayout.tsxowns the shared route chrome and route-change scroll-to-top behavior.
Multi-App SSR Demo
Files live in demos/multi-app-ssr. Demonstrates running two independently-built React apps (App A and App B) behind a single SSR server, with cookie-based routing to switch between them, plus App C as an intentionally non-existent app that triggers a 500 error page with a cookie-clear button.
Real-world use cases for multi-app SSR: consolidating multiple sites on shared server infrastructure rather than running separate servers for each. For example, a SaaS product can serve a marketing site and the authenticated app workspace from one process, or serve multiple customer-facing sites that share backend API handlers and plugins. Cookies, plugins, and page data handlers are shared across apps, so shared concerns (auth, analytics, rate limiting) register once. Per-app concerns (UI bundle, HTML template, CDN URL, publicAppConfig) are isolated.
These demo apps are intentionally simpler than demos/ssr: no dark mode, no theme plugin, no page data loaders. The focus is multi-app routing. That said, apps on the same server share Unirend context, so in a real multi-app setup you can share theme cookies and other cross-app concerns consistently.
From the repo root (using package scripts):
# Development SSR with Vite HMR for both apps simultaneously.
bun run multi-ssr:serve:hmr
# Production: build both app bundles, then run the built server.
bun run multi-ssr:build
bun run multi-ssr:serve:prod
# Or do both in one step.
bun run multi-ssr:build-and-serve:prodWhat this shows:
- Registering a second app with
registerHMRApp/registerBuiltApp. - Cookie-based app routing via
request.setActiveSSRApp()in anonRequesthook. - Selecting an unregistered app key (
app-c) triggers the caught error path, which serves a custom 500 HTML page with a "Clear App & Go Home" button that deletes the cookie and redirects. - Each app has its own
publicAppConfig(app name, accent color) and its own Vite build output. AppSwitchercomponent shared across both app bundles via direct source import.
API Server Demo
From the repo root:
bun run api-demoStatic Content Demo
From the repo root:
bun run api-static-demoWebSocket Demo
From the repo root:
bun run ws-demoNote: The WebSocket demo spins up its own SSR + API server on different ports than the main SSR demo. Pages that use data loaders will show a connection error, as the demo's API endpoints are not wired up. The focus here is the WebSocket functionality, not data loading.
Data Loaders
Unirend centralizes route data fetching through a single loader system. See the dedicated Data Loaders guide.
What it covers:
- Page type handler (HTTP/short‑circuit) loader
- Local data loader (SSG‑friendly)
- Using loaders in React Router
- Error transformation/config, redirects, and auth handling
- Configuration (timeouts, connection messages, status mapping, allowed redirect origins, login handling)
API Envelope Structure
See the canonical spec in docs/api-envelope-structure.md for the standardized response envelopes Unirend uses.
- Page data loaders: Expect and return the documented Page Response Envelope. When a backend returns an API envelope, the loader should transform it to a page envelope as needed (preserving metadata and handling redirects/authentication per the spec).
- AJAX/fetch and form posts: Use the API Response Envelope. This is the recommended standard across your application so client code can handle success and error states consistently.
Helpers and Integration
The following helpers and integrations make it easy to work with these envelopes throughout your application.
- Server middleware/plugins: The
SSRServerandserveAPIplugin systems are designed to work with these envelopes (including default error/not-found handling). Use the middleware/plugin APIs exposed byunirend/serverto register your routes. - Helper utilities: Import helpers to construct envelopes and validate requests at your API handlers:
- Import path:
import { APIResponseHelpers } from 'unirend/api-envelope' - Key helpers:
createAPISuccessResponse,createAPIErrorResponse,createPageSuccessResponse,createPageErrorResponse,createPageRedirectResponse,ensureJSONBody,ensureURLEncodedBody,ensureMultipartBody, and type guards likeisSuccessResponse,isErrorResponse,isRedirectResponse,isPageResponse,isValidEnvelope.
- Import path:
Error Handling
See setup recommendations and how the framework handles SSR vs client errors in the dedicated guide: docs/error-handling.md.
File Upload Helpers
Unirend provides first-class support for file uploads with streaming validation, cleanup handlers, and proper error responses.
Key features:
- Streaming validation with mid-stream abort if limits exceeded
- Automatic cleanup handlers for partial uploads
- Fail-fast behavior for batch uploads
- Works seamlessly with the envelope pattern
Quick Start:
import { serveSSRWithHMR, processFileUpload } from 'unirend/server';
import { APIResponseHelpers } from 'unirend/api-envelope';
const server = serveSSRWithHMR(sourcePaths, {
fileUploads: { enabled: true },
});
server.api.post('upload/avatar', async (request, reply, params) => {
const result = await processFileUpload({
request,
reply,
maxSizePerFile: 5 * 1024 * 1024,
allowedMimeTypes: ['image/jpeg', 'image/png'],
processor: async (fileStream, metadata, context) => {
// Stream to storage and return metadata
const url = await saveToStorage(fileStream);
return { url, filename: metadata.filename };
},
});
if (!result.success) {
return result.errorEnvelope;
}
return APIResponseHelpers.createAPISuccessResponse({
request,
data: { file: result.files[0].data },
statusCode: 200,
});
});Full Documentation: docs/file-upload-helpers.md
Includes comprehensive examples for S3 uploads, security best practices, temporary upload folders, auth integration, and more.
UX Suggestions
Scroll to top on navigation
- Add a lightweight scroll-to-top effect in a common component like your header or app layout.
- Example: see
demos/ssg/components/Header.tsx.
useEffect(() => { window.scrollTo({ top: 0, behavior: 'instant' }); }, [location.pathname]);Scroll to top for standalone application error pages
- When rendering a top-level application error (caught by the error boundary), include a scroll-to-top on mount so it doesn’t depend on your normal layout, as recommended.
- Example: see
demos/ssr/components/error-pages/ApplicationError.tsx.
Development
Unirend is built with TypeScript and uses modern JavaScript features.
# Install dependencies
bun install
# Build the project
bun run build
# Run tests
bun testWhen preparing a new release:
- Update the version in
package.json - Run the build command, which will automatically update docs (TOCs and README version)
# Build the project (includes docs/TOC updates and README version sync)
bun run buildThe build process uses the update-docs script defined in package.json. It updates TOCs (README, CHANGELOG, and API envelope doc) and runs scripts/update-readme-version.ts to synchronize the version number in the README with the one in package.json. Afterwards, you can publish the package to npm:
# Publish to npm
bun publishAfter publishing, commit the generated file changes back to Git. The build updates src/version.ts, the README title, and the TOCs.
Parking Lot
- Possible future PWA helpers: installability, service worker registration, app shell/static asset caching, optional offline page data handlers, and standard offline page envelopes. Notes are collected in docs/pwa.md.
Build Info Utilities
See docs/build-info.md for generating and loading build metadata (version, Git hash/branch, timestamp).
Utilities
Unirend exposes public utilities for static file caching, HTML escaping, and runtime requirement checks. Some are used internally by unirend, while others are intended for use in your own server or build scripts:
- StaticContentCache: A caching layer for static file serving with ETag support and LRU caching
- escapeHTML / escapeHTMLAttr: Safe HTML escaping for server-side HTML generation (e.g. custom error pages,
dangerouslySetInnerHTML) - getRuntimeSupportInfo / isSupportedRuntime / assertSupportedRuntime: Check for
Node >= 25unless the current runtime is Bun
import {
StaticContentCache,
escapeHTML,
escapeHTMLAttr,
getRuntimeSupportInfo,
} from 'unirend/utils';See docs/utilities.md for full API documentation.
