npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

reroute-js

v0.45.11

Published

Reroute is a feature-rich file-based routing framework for building fullstack React apps on top of Bun and Elysia.

Readme

Reroute

Reroute is a feature-rich file-based routing framework for building fullstack React apps on top of Bun and Elysia.

📚 Table of Contents

✨ Features

🎯 Core Framework

  • Built on Bun - Lightning-fast JavaScript runtime for everything
  • 🔄 File-Based Routing - Automatic route generation from your file structure
  • 🎨 Server-Side Rendering (SSR) - SEO-friendly React rendering on the server
  • Streaming SSR - Progressive rendering with React 19 for 50-70% faster TTFB
  • 🔥 Live Reload - Live reload in development mode with instant feedback
  • 📦 Zero Config - Works out of the box with sensible defaults

🛠️ CLI Tools

  • 🚀 reroute init - Scaffold new projects with templates (basic, blog, store)
  • 🔨 reroute gen - Generate content registry and route artifacts
  • reroute dev - Start development environment with side-by-side logs and colored output
  • 📊 reroute analyze - Bundle analyzer with dependency treemap and size analysis

📄 Content Management

  • 📚 Content Collections - Organize content by collections (blog posts, docs, etc.)
  • 🔍 Content Discovery - Automatic scanning and indexing of content files
  • 🏷️ Metadata Extraction - Support for content metadata
  • 📑 Content Registry - Pre-generated content index for fast lookups
  • 🎯 Dynamic Imports - Code-split content chunks for optimal loading
  • 📦 Collection Chunking - Individual module bundles per content item

📝 Markdown & MDX Support

  • 📄 Native Markdown Routes - Drop .md or .mdx files directly in your routes directory
  • 🎨 Syntax Highlighting - Beautiful code blocks powered by Shiki with VS Code themes
  • 📋 Frontmatter Support - YAML frontmatter for page metadata and multiline values using | pipe notation
  • ⚛️ True MDX Support - Import and use React components inside markdown with @mdx-js/mdx
  • 🧩 Component Composition - Mix JSX components with markdown content seamlessly
  • 🔧 Zero Config - Automatic detection and processing when streamdown is installed
  • 🎯 GitHub Flavored Markdown - Built-in tables, task lists, strikethrough, code highlighting, and more
  • Streamdown Powered - AI-optimized streaming markdown with complete feature set built-in
  • 🧩 Markdown Component - Programmatic markdown rendering with <Markdown> component

🔍 Search & Table of Contents

  • 🔍 Full-Text Search - Build-time index generation with smart ranking (title → headings → metadata)
  • 🎯 Client-Side Search - useSearch() hook with debouncing, pagination, and real-time results
  • 🌐 SSR Search - Server-rendered search pages with collectionSearch() for SEO
  • 📊 Search Endpoint - HTTP API at /__reroute_search for external integrations
  • 📄 Pagination - Configurable page size with full pagination support
  • 📑 Auto TOC - Table of contents extracted from markdown headings during build
  • 🎯 Auto-Detection - useToc() automatically detects collection and slug from route
  • Anchor Scrolling - Smooth scroll to sections with fragment navigation
  • 💾 Lightweight - Optional content inclusion (default: headings + metadata only)
  • 🎛️ Flexible - Filter by collections, configure heading levels

⚛️ React Integration

  • React 19 - Built with the latest React version for optimal performance
  • 🪝 Router Hooks:
    • useNavigate() - Programmatic navigation
    • useParams() - Access route parameters
    • useSearchParams() - Query string manipulation
    • useRouter() - Access router state and utilities
  • 📚 Content Hooks:
    • useContent() - Load and manage content collections with MongoDB-style filtering and sorting
    • useFeed() - Discover RSS feed URLs for current route
    • useLlms() - Discover LLM-friendly content URLs for current route
    • useSearch() - Client-side content search with debouncing
    • useToc() - Auto-extract table of contents from markdown headings
  • 🧩 Components:
    • <Link> - Client-side navigation with automatic prefetching
    • <Image> - Optimized images with automatic format negotiation and lazy loading
    • <Outlet> - Render nested route components
    • <ContentRoute> - Dynamic content rendering with metadata injection
    • <ClientOnly> - Client-side only rendering with optional layout preservation (className/style props)
  • 🎭 Providers:
    • <RerouteProvider> - USE THIS - All-in-one provider with routing + content + artifacts
    • <RouterProvider> - Low-level router only (internal use, prefer RerouteProvider)
    • <ContentProvider> - Content system context (internal use)
  • 🗂️ SSR Data:
    • useData() - Read route-level SSR data without loaders
    • export const ssr = { data() {} } - Route data function executed on server

🖼️ Image Optimization

  • 🎨 Format Control - Support for auto, AVIF, WebP, JPEG, and PNG formats
  • 📐 Responsive Images - Automatic srcset generation for multiple device sizes (640-3840px)
  • On-Demand Processing - Server-side image transformation using Sharp
  • 💾 Smart Caching - In-memory and disk cache for optimized images (1-year cache headers)
  • 🔍 Lazy Loading - Native lazy loading with IntersectionObserver fallback for older browsers
  • Priority Loading - Opt-in eager loading for above-the-fold images
  • 🌫️ Blur Placeholder - Optional blur-up effect with custom blur data URL support
  • 🎯 Quality Control - Configurable quality (1-100, default: 75)
  • 🔄 Loading States - Built-in spinner with customization (size, color, background, custom component)
  • 🎨 Custom Loader - Override default image URL generation
  • 📏 Callbacks - onLoad and onError event handlers
  • 📱 Sizes Attribute - Control responsive image selection with custom sizes

🎨 OG Image Generation

  • 🖼️ Auto-Generate - Dynamic Open Graph images for social media previews
  • Lightweight - Uses @vercel/og (Satori + resvg) - only ~5MB, no Chrome dependency
  • 🎨 React Components - Design OG images with React and inline styles
  • 🔄 Runtime Generation - Generate images on-demand with smart caching
  • 📝 Frontmatter Support - Auto-extract title, description, author, date from content
  • 🎯 Route Colocated - Place [og].tsx files alongside routes
  • 🖼️ Avatar Support - Add logos/avatars from external URLs or local files
  • ⚙️ Per-Route Override - Customize via export const ssr = { ogImage: { ... } }
  • 🎭 Default Template - Built-in template or configure your own
  • 🏷️ Auto Meta Tags - Automatic injection of og:image and twitter:card tags
  • 🎯 CLI Preview - reroute og command to preview images in browser

🚀 Performance Optimizations

  • 💾 Smart Caching - LRU cache for files and bundles
  • 🔗 Link Prefetching - Automatic content prefetching on hover/focus
  • 📦 Bundle Hashing - Content-based hashing for optimal cache invalidation
  • Module Preloading - Browser hints for critical resources
  • 🎯 SSR Module Seeding - Pre-populate modules for instant hydration
  • 📊 Collection Inlining - Inline collection data for zero-latency rendering
  • 🌊 Progressive Streaming - Shell HTML sent immediately while data loads
  • 🎭 Automatic Suspense - Zero-config streaming wrappers via [skeleton].tsx convention
  • React 19 Streaming - Native renderToReadableStream for optimal performance

🎨 Styling & Theming

  • 🎨 Tailwind CSS v4 Support - Automatic detection and compilation with CSS-first configuration
  • Live CSS Reload - Instant style updates in development mode
  • 🎯 Modern CSS Features - @theme and @utility directives support
  • 🚀 Oxide Engine - 5x faster builds with the new Tailwind compiler
  • 🔧 Zero Config - Works out of the box when @tailwindcss/cli is installed

🎨 Developer Experience

  • 📁 Project Templates - Quick start with basic, blog, or store templates
  • 🔄 Live Reload - Server-Sent Events (SSE) for instant browser updates
  • 🎯 TypeScript Support - Full type safety throughout the stack
  • 🗂️ File System Watcher - Automatic rebuilds on source changes
  • 🎪 404 Handling - Custom NotFound routes with pattern matching
  • 🏗️ Layout System - Shared layouts across route groups

🌐 Server Features (Elysia Plugin)

  • 🔌 Elysia Plugin - Drop-in plugin for Elysia apps
  • 🎨 SSR Routes - Server-rendered React pages
  • 📄 Static File Serving - Optimized bundle delivery
  • 📚 Content API Routes - Dynamic content endpoints
  • 🛠️ Dev Mode Routes - Development tooling and live reload
  • 📦 Artifact Routes - Serve generated content chunks and registry
  • 📊 Optional OpenTelemetry - Built-in tracing for SSR, 404s, cache hits

🎯 Build System

  • 🔧 Transpilation - TypeScript/JSX to optimized JavaScript
  • 🗜️ Minification - Optional code minification for production
  • 🗺️ Source Maps - Debug support with source map generation
  • 📦 Module Bundling - Efficient code splitting and bundling
  • 🎯 Tree Shaking - Remove unused code automatically

🎨 Head Management

  • 🏷️ Meta Tags - Automatic meta tag injection from content metadata
  • 📝 Per-Page Head - Route-specific head elements
  • 🌍 Language Attribute - Dynamic lang attribute support
  • 🎯 SEO Optimization - Title, description, and custom meta tags

🗺️ Sitemap Generation

  • 🔍 Auto-Discovery - Automatically discovers static routes, content collections, and ssr.data routes
  • 📄 XML Generation - Standards-compliant sitemap.xml following Google's specifications
  • 💾 Smart Caching - Generated on first request and cached according to maxAge
  • 📊 Auto-Pagination - Automatically splits into multiple files for sites with 50k+ URLs
  • 🎨 Custom Extractors - Content-agnostic URL and date extraction via configuration
  • 📅 Metadata Support - Includes lastmod, changefreq, and priority for each URL

🤖 Robots.txt Generation

  • 🛡️ Smart Defaults - Permissive by default with automatic sitemap references
  • 🔗 Deep Integration - Auto-disallows sitemap excludes and redirect sources
  • 🎯 Policy Helpers - Pre-configured functions to block/allow AI crawlers and search engines
  • 🧩 Composable - Mix and match policies for fine-grained crawler control
  • Runtime Generation - Generated on demand with 24-hour caching
  • 🤝 Standards Compliant - Follows RFC 9309 robots.txt specification

📰 RSS Feed Generation

  • 🔍 Auto-Discovery - Discovers content collections, route ssr.data, and layout ssr.data
  • 📄 RSS & Atom Support - Standards-compliant RSS 2.0 and Atom 1.0 formats
  • 📚 Per-Collection Feeds - Each collection gets its own feed (e.g., /blog/feed.xml)
  • 💾 Smart Caching - Generated on first request and cached according to maxAge
  • 🎨 Custom Extractors - Content-agnostic extraction for title, description, author, pubDate

🧠 LLM-Friendly Content

  • 📄 Multiple Formats - Serve routes as .txt or .md extensions (e.g., /blog/post.txt)
  • 🤝 Content Negotiation - RESTful Accept header support (text/plain, text/markdown)
  • 📋 Site Index - Auto-generated /llms.txt with all content organized by section
  • 📦 Full Bundle - /llms-full.txt with complete site content in one file
  • 🎯 Token-Efficient - Minimal metadata, maximum useful content for AI consumption
  • 🔒 Exclusions - Filter routes/collections with strings, RegExp, or custom functions
  • Smart Caching - 24h for pages, 7d for bundles (configurable)
  • 🔄 Auto-Sorting - Items sorted by publication date (newest first)
  • 📏 Limit Control - Configurable maximum items per feed (default: 50)

📦 Configuration Options

  • 🎯 Custom Assets Directory - Configure where your client files live
  • 🔧 URL Prefix - Deploy to subdirectories with custom prefixes
  • 🚫 Ignore Patterns - Exclude files from static serving
  • 🏷️ Custom Headers - Add headers to static file responses
  • Cache Control - Configurable max-age and cache directives for static files and __reroute_data JSON
  • 🎨 HTML Template - Custom index.html with variable substitution
  • 🔐 Environment Variables - Share REROUTE_ prefixed env vars between server and browser

🚀 Quick Start

# Create a new project (basic template)
bunx reroute-js init my-app

# Or with a specific template
bunx reroute-js init my-blog --template blog
bunx reroute-js init my-store --template store

# Navigate to project
cd my-app

# Start development
bun dev

🔌 External Requests SSR

Reroute can execute route-level data fetching on the server and inline the result for instant hydration. Define an optional ssr.data in a route module and read it with useData():

// routes/products/[id].tsx
import { useData, useParams } from 'reroute-js/react';
import { getProduct } from '../lib/api';

export const ssr = {
  async data({ params, set }: { params: { id?: string }; set: { status: number } }) {
    const id = Number.parseInt(params.id || '');
    if (!Number.isFinite(id)) {
      set.status = 404;  // Set HTTP status code
      return { product: null };
    }
    
    const product = await getProduct(id);
    if (!product) {
      set.status = 404;
      return { product: null };
    }
    
    return { product };
  },
};

export default function Page() {
  const { id } = useParams<{ id: string }>();
  const data = useData<{ product: any }>(); // keyed by pathname
  return <div>{data?.product?.title || `Invalid product ${id}`}</div>;
}

Notes:

  • Works with Bun + Elysia, no client loaders needed for initial render
  • Data is injected as window.__REROUTE_DATA__ and read during hydration
  • Use with existing content features (useContent); both can be seeded in the same page
  • HTTP Status Control: Use set.status to set response status codes (follows Elysia's API). Non-200 responses are not cached

⚡ Streaming SSR

Reroute supports progressive server-side rendering with React 19's streaming capabilities. Routes with async data automatically stream shell content immediately (50-70% faster TTFB), then progressively send content as it becomes ready.

Quick Start

Enable streaming in your config:

// reroute.config.ts
import { defineConfig } from 'reroute-js/core';

export default defineConfig({
  app: {
    src: './src/client/App',
  },
  streaming: {
    enabled: true,
  },
});

Run bun gen and routes with ssr.data automatically get Suspense wrappers and streaming optimization - no code changes needed!

How It Works

Without streaming (default):

  • Server waits for all data → sends complete HTML → user sees page

With streaming:

  1. Server sends shell HTML immediately (layouts, navigation) - instant TTFB
  2. Shows loading skeleton while data fetches
  3. Streams content progressively as ready
  4. User sees something immediately, perceives faster load

Custom Loading Skeletons

Create custom loading states using the [skeleton].tsx convention:

// routes/blog/[slug].tsx
export const ssr = {
  async data({ params }) {
    return { post: await fetchPost(params.slug) };
  }
};

export default function BlogPost() {
  const { post } = useData();
  return <article>{post.title}</article>;
}
// routes/blog/[skeleton].tsx
export default function BlogSkeleton() {
  return (
    <div className="animate-pulse p-8">
      <div className="h-8 bg-gray-200 rounded w-3/4 mb-4" />
      <div className="space-y-3">
        <div className="h-4 bg-gray-200 rounded" />
        <div className="h-4 bg-gray-200 rounded" />
      </div>
    </div>
  );
}

The CLI automatically detects [skeleton].tsx files and uses them in generated Suspense boundaries.

Layout Streaming

Layouts can also stream with their own data and skeletons:

// routes/dashboard/[layout].tsx
export const ssr = {
  async data() {
    return { user: await fetchUser() };
  }
};

export default function DashboardLayout() {
  const { user } = useLayoutData();
  return (
    <div>
      <nav>Welcome, {user.name}</nav>
      <Outlet />
    </div>
  );
}
// routes/dashboard/[layout+skeleton].tsx
export default function DashboardLayoutSkeleton() {
  return <div className="animate-pulse">Loading dashboard...</div>;
}

Streaming order:

  1. Shell HTML → instant
  2. Layout skeleton → while layout data loads
  3. Layout content → when ready
  4. Route skeleton → while route data loads
  5. Route content → when ready

Default Skeletons

Configure app-wide default skeletons:

// reroute.config.ts
export default defineConfig({
  streaming: {
    enabled: true,
    defaultSkeleton: './src/client/components/DefaultSkeleton',
    defaultLayoutSkeleton: './src/client/components/LayoutSkeleton',
  },
});

Priority order:

  1. Custom [skeleton].tsx in route directory
  2. Configured defaultSkeleton
  3. Built-in generic skeleton

For layouts:

  1. Custom [layout+skeleton].tsx
  2. Configured defaultLayoutSkeleton
  3. Falls back to defaultSkeleton
  4. Built-in generic skeleton

Nested Suspense

Add your own Suspense boundaries for fine-grained streaming:

// routes/feed.tsx
import { Suspense } from 'react';

export const ssr = {
  async data() {
    return { posts: await fetchPosts() };
  }
};

export default function Feed() {
  const { posts } = useData();
  
  return (
    <div>
      <h1>Feed</h1>
      {/* Route-level Suspense added automatically by gen */}
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          {/* Component-level Suspense you can add manually */}
          <Suspense fallback={<div>Loading comments...</div>}>
            <Comments postId={post.id} />
          </Suspense>
        </article>
      ))}
    </div>
  );
}

Each Suspense boundary streams independently - shell renders first, then each boundary resolves progressively.

Performance

Example improvements with streaming enabled:

| Metric | Without Streaming | With Streaming | Improvement | |--------|-------------------|----------------|-------------| | TTFB | 400ms | 50ms | 87% faster | | FCP | 600ms | 150ms | 75% faster |

Example

Check out examples/streaming for a comprehensive demo with:

  • Basic streaming routes
  • Custom skeletons
  • Layout streaming
  • Nested Suspense boundaries
  • Performance comparisons

🎭 Client-Only Rendering

The ClientOnly component prevents hydration mismatches for browser-specific code and supports layout preservation to prevent flickering:

import { ClientOnly } from 'reroute-js/react';

// Basic usage - prevent hydration mismatch
<ClientOnly fallback={<div>Loading...</div>}>
  <CountdownTimer />
</ClientOnly>

// Prevent layout shift with height preservation
<ClientOnly 
  fallback={<PricingSkeleton />}
  className="min-h-screen"
>
  <PricingSection />
</ClientOnly>

// With inline styles
<ClientOnly 
  fallback={<Loader />}
  style={{ minHeight: '600px' }}
>
  <DashboardContent />
</ClientOnly>

When to use:

  • Components using browser APIs (window, localStorage, navigator)
  • Dynamic values (Date.now(), Math.random())
  • Time-sensitive data that differs between server and client
  • Large lazy-loaded components (use className/style to preserve height)

Props:

  • fallback - Content to show during SSR and before hydration
  • className - Optional className for wrapper div (useful for layout preservation)
  • style - Optional inline styles for wrapper div (useful for layout preservation)

🔐 Environment Variables

Reroute supports sharing environment variables between server and browser code using a REROUTE_ prefix convention (similar to how Vite uses VITE_).

Usage

Create a .env file in your project root:

# .env

# Server-only variables (not exposed to browser)
DATABASE_URL=postgresql://localhost:5432/mydb
SECRET_KEY=my-secret-key

# Public variables (exposed to browser)
REROUTE_API_URL=https://api.example.com
REROUTE_ANALYTICS_ID=G-XXXXXXXXXX
REROUTE_FEATURE_FLAG=true

Accessing Variables

In Server Code: All variables are available via process.env or Bun.env:

// src/index.ts
const dbUrl = process.env.DATABASE_URL;
const apiUrl = process.env.REROUTE_API_URL;

In Client/Browser Code: Only REROUTE_ prefixed variables are available:

// src/client/routes/index.tsx
export default function Home() {
  // ✅ Available - prefixed with REROUTE_
  const apiUrl = import.meta.env.REROUTE_API_URL;
  const analyticsId = process.env.REROUTE_ANALYTICS_ID; // Also works

  // ❌ Not available - no REROUTE_ prefix
  const dbUrl = process.env.DATABASE_URL; // undefined in browser

  return <div>API: {apiUrl}</div>;
}

Environment Files

Reroute supports multiple environment files with priority:

  • .env - Base environment variables
  • .env.production - Production-specific variables
  • .env.local - Local overrides (gitignored)
  • .env.production.local - Local production overrides (gitignored)

Files are loaded in order, with later files overriding earlier ones.

Important: The mode (development vs production) is controlled by the --prod flag, not by NODE_ENV in .env files. Use reroute gen --prod or reroute build for production mode.

Hot Reload: Changes to any .env file automatically rebuild the bundle and reload your browser in watch mode - no server restart needed!

Security

Important: Only variables prefixed with REROUTE_ are bundled into the client code. This prevents accidentally exposing sensitive credentials like database URLs, API keys, or secrets to the browser.

  • REROUTE_API_URL - Safe to expose
  • REROUTE_FEATURE_FLAG - Safe to expose
  • DATABASE_URL - Server-only
  • SECRET_KEY - Server-only

TypeScript Support

For type safety, create a env.d.ts file:

// env.d.ts
interface ImportMetaEnv {
  readonly REROUTE_API_URL: string;
  readonly REROUTE_ANALYTICS_ID: string;
  readonly REROUTE_FEATURE_FLAG: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

📝 Markdown Routes

Reroute supports native markdown and MDX files as routes with frontmatter support and syntax highlighting.

Installation

# Required for markdown support (includes GFM, syntax highlighting, math, mermaid, and more)
bun add streamdown

# Optional: Syntax highlighting (if not using Streamdown's built-in)
bun add -d @shikijs/rehype

# Optional: MDX support (use React components in markdown)
bun add @mdx-js/mdx

Creating Markdown Routes

Simply create .md files in your routes directory:

<!-- src/client/routes/about.md -->
---
title: About Us
description: Learn more about our company
date: 2025-01-15
---

# About Us

Welcome to our **amazing** company! We build great things.

## Our Mission

We strive to create the best developer experience possible.

```typescript
// Example code block with syntax highlighting
const greeting = "Hello, World!";
console.log(greeting);

The route will be automatically available at `/about` with:
- Automatic `<title>` and `<meta>` tag generation from frontmatter
- Built-in syntax highlighting for code blocks with Streamdown
- Built-in GitHub Flavored Markdown, math expressions, and mermaid diagrams

### Draft Content Support

Mark content as draft to exclude it from production builds:

```markdown
<!-- src/client/routes/blog/content/my-draft.md -->
---
title: Work in Progress
description: This post is not ready yet
draft: true
---

# Coming Soon

This content will be excluded from production builds.

Draft Behavior:

  • Development mode (reroute gen, reroute dev): Draft content is included in content registry and search index
  • Production mode (reroute gen --prod, reroute build): Draft content is completely excluded from:
    • Content registry and bundles
    • Search index
    • Sitemap generation
    • RSS feeds
    • All other discovery mechanisms

This allows you to work on content without deploying it to production.

Creating MDX Routes with Components

For interactive content with React components, use .mdx files. Best practice: Import components from external files rather than defining them inline.

<!-- src/client/routes/blog/content/my-post.mdx -->
---
title: Interactive Demo
description: MDX with React components
date: 2025-01-17
steps: |
  Install dependencies
  Configure the project
  Run the development server
---

import { Link } from 'reroute-js/react';
import { Alert } from '../../../components/Alert';
import { StepsList } from '../../../components/StepsList';

# Interactive Content

The `meta` variable is automatically available from frontmatter:

<StepsList steps={meta.steps} title="Getting Started" />

<Alert type="success">
  Components imported from external files work seamlessly!
</Alert>

<Link to="/">Go Home</Link>

Key Features:

  • Import external components from separate files (recommended)
  • Frontmatter available as meta variable automatically
  • Import paths automatically rewritten during compilation
  • Supports multiline YAML values with | pipe notation
  • Full TypeScript support for imported components

MDX Support requires:

  • @mdx-js/mdx package installed
  • .mdx file extension
  • Components imported at the top, after frontmatter.

Programmatic Markdown Rendering

Use the <Markdown> component to render markdown content dynamically:

import { Markdown } from 'reroute-js/react';

export default function MyPage() {
  const content = `# Hello\nThis is **markdown** content.`;

  return (
    <Markdown
      theme="github-dark"
      gfm={true}
      highlight={true}
    >
      {content}
    </Markdown>
  );
}

Features

  • Frontmatter: YAML metadata for SEO and content organization
  • Syntax Highlighting: Powered by Shiki via @shikijs/rehype with VS Code themes
  • GFM Support: Tables, task lists, strikethrough, autolinks
  • MDX Support: Import and use React components in .mdx files
  • Component Composition: Mix markdown with JSX seamlessly
  • Type Safety: Full TypeScript support
  • Zero Config: Automatic detection when packages are installed
  • Optional: Only bundle dependencies you actually use

🗺️ Sitemap & RSS Feed Generation

Reroute provides automatic sitemap and RSS feed generation with zero configuration required.

Sitemap Generation

Generate standards-compliant XML sitemaps for search engines.

Quick Start

// reroute.config.ts
import { defineConfig } from 'reroute-js/core';

export default defineConfig({
  sitemap: {
    enabled: true,  // opt-in (default: false)
    baseUrl: 'https://your-domain.com',
  },
});

Your sitemap is now available at /sitemap.xml!

What Gets Included

All three sources are automatically discovered:

  1. Static Routes - Pages in routes/ folder (e.g., /, /about, /contact)
  2. Content Collections - Items in routes/[collection]/content/ with metadata
  3. Route SSR Data - Dynamic data from route ssr.data exports

Custom Configuration

For non-standard data structures:

sitemap: {
  enabled: true,
  baseUrl: 'https://your-domain.com',
  
  // Extract URL segment from ssr.data items
  extractUrl: (item, routePattern) => {
    // Products use SKU instead of slug
    if (routePattern === '/products' && item.sku) {
      return item.sku;  // /products/laptop-pro-2024
    }
    return null; // fall back to defaults (slug, id, name, key)
  },
  
  // Extract lastmod date
  extractLastmod: (item) => item.updatedAt || item.date,
  
  // Default settings
  changefreq: 'weekly',   // or 'daily', 'monthly', etc.
  priority: 0.5,          // 0.0 to 1.0
}

Features

  • Runtime generation - Generated on first request, cached according to maxAge
  • Auto-pagination - Sites with 50k+ URLs automatically split into multiple files with sitemap index
  • SEO-friendly - Includes <lastmod>, <changefreq>, and <priority> tags
  • Content-agnostic - Works with any data structure via custom extractors
  • Smart caching - Respects your maxAge configuration

Robots.txt Generation

Control crawler access with automatic robots.txt generation and smart defaults.

Quick Start

// reroute.config.ts
import { defineConfig, blockAICrawlers } from 'reroute-js/core';

export default defineConfig({
  robots: {
    enabled: true,
    baseUrl: 'https://your-domain.com',
    policies: [
      { userAgent: '*', allow: ['/'], crawlDelay: 10 },
      ...blockAICrawlers(), // Block all AI crawlers
    ],
  },
});

Your robots.txt is available at /robots.txt!

Policy Helpers

import {
  blockAICrawlers,
  allowAICrawlers,
  blockAITrainingCrawlers,
  allowSearchCrawlers,
  blockSearchCrawlers,
  AI_CRAWLERS,
  SEARCH_CRAWLERS,
} from 'reroute-js/core';

// Block all AI crawlers
...blockAICrawlers()

// Block specific AI crawlers
...blockAICrawlers([AI_CRAWLERS.GPT, AI_CRAWLERS.CLAUDE])

// Block only training crawlers (CCBot, Google-Extended, etc.)
...blockAITrainingCrawlers()

// Allow all search engines
...allowSearchCrawlers()

// Block specific search engines
...blockSearchCrawlers([SEARCH_CRAWLERS.BAIDUSPIDER])

Features

  • Smart defaults - Permissive by default (Allow: /)
  • Sitemap integration - Auto-references /sitemap.xml when sitemap is enabled
  • Redirect exclusion - Auto-disallows old redirect source paths
  • Sitemap exclusion - Auto-disallows routes excluded from sitemap
  • Policy helpers - Pre-configured functions for common crawler control
  • Composable - Mix and match policies for fine-grained control
  • Standards compliant - Follows RFC 9309 robots.txt specification

RSS Feed Generation

Generate RSS 2.0 and Atom 1.0 feeds for content syndication.

Quick Start

// reroute.config.ts
import { defineConfig } from 'reroute-js/core';

export default defineConfig({
  rss: {
    enabled: true,  // opt-in (default: false)
    baseUrl: 'https://your-domain.com',
    title: 'My Blog',
    description: 'Latest posts and updates',
  },
});

Your feeds are automatically available at:

  • /feed.xml - Main feed (combines all sources below)
  • /blog/feed.xml - Blog collection (from routes/blog/content/)
  • /changelog/feed.xml - Changelog route (from route's ssr.data)
  • /announcements/feed.xml - Announcements (from layout's ssr.data)

Pattern:

  • /[collection]/feed.xml - For any content collection
  • /[route]/feed.xml - For any route/layout with ssr.data

What Gets Included

All three sources are automatically discovered:

  1. Content Collections - Items from routes/[collection]/content/ folders
    • Example: Blog posts with metadata (title, description, date)
  2. Route SSR Data - Arrays returned by route ssr.data functions
    • Example: Changelog versions fetched from API
    • Must export ssr.data that returns object containing arrays
  3. Layout SSR Data - Arrays returned by layout ssr.data functions
    • Example: Site-wide announcements fetched in layout
    • Must export ssr.data in [layout].tsx that returns object containing arrays

Important: For ssr.data discovery, the data must contain arrays. Nested arrays (e.g., { user: { notifications: [...] } }) are automatically found via recursive discovery.

Custom Configuration

rss: {
  enabled: true,
  baseUrl: 'https://your-domain.com',
  title: 'My Blog',
  description: 'Latest updates',
  limit: 50,           // max items per feed (default: 50)
  format: 'rss',       // 'rss' (RSS 2.0) or 'atom' (Atom 1.0)
  
  // Extract URL segment from ssr.data items
  extractUrl: (item, routePattern) => {
    // Changelog uses version field
    if (routePattern === '/changelog' && item.version) {
      return item.version;  // /changelog/1.2.0
    }
    return null; // fall back to defaults (slug, id, name, key)
  },
  
  // Extract publication date
  extractPubDate: (item) => item.publishedAt || item.date || item.createdAt,
  
  // Extract author
  extractAuthor: (item) => item.author || item.authorName,
  
  // Extract full content (optional, for full-text feeds)
  extractContent: (item) => item.body || item.content,
}

Features

  • Multi-source - Combines content collections, route ssr.data, and layout ssr.data
  • Per-collection feeds - Each collection gets /[collection]/feed.xml
  • Per-route feeds - Routes with ssr.data get /[route]/feed.xml
  • RSS & Atom - Choose RSS 2.0 or Atom 1.0 format
  • Auto-sorting - Items sorted by pubDate (newest first)
  • Smart caching - Generated on first request, cached according to maxAge
  • Recursive discovery - Finds arrays even in nested data (e.g., user.notifications)
  • Content-agnostic - Works with any data structure via custom extractors

Discovering Feeds in Your UI

Use the useFeed() hook to automatically discover and link to RSS feeds:

import { useFeed } from 'reroute-js/react';

function Header() {
  const feed = useFeed();
  
  return (
    <header>
      {/* Show collection/route-specific feed if available */}
      {feed.hasFeed && (
        <a href={feed.feedUrl!} target="_blank">
          Subscribe to {feed.collection}
        </a>
      )}
      
      {/* Main feed always available */}
      <a href={feed.mainFeedUrl}>RSS Feed</a>
    </header>
  );
}

Returns:

  • feedUrl - URL of the specific feed (/blog/feed.xml, /changelog/feed.xml, etc.) or null
  • mainFeedUrl - Main feed URL (always /feed.xml)
  • collection - Collection/route name ('blog', 'changelog', etc.) or null
  • hasFeed - Boolean indicating if current route has a specific feed

SPA Navigation Support

Important: When using layout ssr.data, use <Link> components (not <a> tags) for proper client-side navigation:

import { Link } from 'reroute-js/react';

// ✅ Good - prefetches layout data on hover
<Link to="/announcements">Announcements</Link>

// ❌ Bad - full page reload, no prefetch
<a href="/announcements">Announcements</a>

LLM-Friendly Content

Make your site AI-navigable with automatic text/markdown formats and content bundles.

Quick Start

// reroute.config.ts
import { defineConfig } from 'reroute-js/core';

export default defineConfig({
  llms: {
    enabled: true,
    baseUrl: 'https://your-domain.com',
    siteName: 'My Blog',
    siteDescription: 'Thoughts on web development',
  },
});

Your site now supports:

  • .txt and .md extensions on any route (e.g., /blog/post.txt, /about.md)
  • Accept header content negotiation (Accept: text/plain, Accept: text/markdown)
  • /llms.txt - Index of all content organized by section
  • /llms-full.txt - Complete site content in one file

How It Works

File Extensions:

# Original HTML route
GET /blog/hello-world → HTML page

# Add .txt for plain text
GET /blog/hello-world.txt → Clean text (title + body)

# Add .md for markdown
GET /blog/hello-world.md → Markdown format

Accept Headers (RESTful):

# Same URL, different representations
curl -H "Accept: text/plain" https://example.com/blog/hello-world
curl -H "Accept: text/markdown" https://example.com/blog/hello-world
curl -H "Accept: text/html" https://example.com/blog/hello-world  # Default

Features

  • Runtime generation - Content extracted on-demand, cached aggressively
  • Smart caching - 24h for pages, 7d for full bundle (configurable)
  • Multi-format - Extensions AND Accept headers (RESTful)
  • Token-efficient - Minimal metadata, maximum useful content
  • Flexible exclusions - Strings, RegExp, or custom functions
  • Works everywhere - Collections, SSR routes, static pages
  • Auto-discovery - All content automatically indexed

Discovering LLM Files in Your UI

Use the useLlms() hook to automatically discover and link to LLM content:

import { useLlms } from 'reroute-js/react';

function Footer() {
  const llms = useLlms();
  
  return (
    <footer>
      {/* Collection/route-specific index if available */}
      {llms.collection && (
        <a href={llms.llmsUrl!}>
          {llms.collection} Index (.txt)
        </a>
      )}
      
      {/* Full content bundle */}
      <a href={llms.fullLlmsUrl}>
        Full Content Bundle
      </a>
      
      {/* Main site index */}
      <a href={llms.mainLlmsUrl}>
        Site Index
      </a>
    </footer>
  );
}

Returns:

  • llmsUrl - Collection/route-specific index (/docs/llms.txt) or main index (/llms.txt)
  • fullLlmsUrl - Full content bundle (/docs/llms-full.txt or /llms-full.txt)
  • mainLlmsUrl - Main site index (always /llms.txt)
  • collection - Collection name ('docs', 'blog') or null
  • hasLlms - Boolean if current route has specific LLM files

🔍 Content Search

Reroute provides full-text search across your content collections with pagination, client-side and SSR support.

Configuration

// reroute.config.ts
import { defineConfig } from 'reroute-js/core';

export default defineConfig({
  search: {
    enabled: true,
    collections: ['blog', 'docs'],  // or omit to index all
    includeContent: false,          // lightweight (headings only)
    metaFields: ['title', 'description', 'excerpt', 'tags'],
    defaultPageSize: 20,            // results per page (default: 20)
    maxPageSize: 100,               // max allowed page size (default: 100)
  },
});

Run reroute gen to generate .reroute/search.json.

Client-Side Search with Pagination

import { useState } from 'react';
import { useSearch, Link } from 'reroute-js/react';

function SearchBox() {
  const [query, setQuery] = useState('');
  const [page, setPage] = useState(1);
  
  const { results, total, totalPages, isLoading } = useSearch({
    query,
    page,
    pageSize: 10,
    collections: ['blog', 'docs'],
  });

  return (
    <div>
      <input
        type="search"
        value={query}
        onChange={(e) => {
          setQuery(e.target.value);
          setPage(1); // reset to first page
        }}
        placeholder="Search..."
      />
      {isLoading && <div>Searching...</div>}
      <ul>
        {results.map(result => (
          <li key={result.id}>
            <Link to={result.href}>{result.title}</Link>
            {result.excerpt && <p>{result.excerpt}</p>}
          </li>
        ))}
      </ul>
      {totalPages > 1 && (
        <div>
          <button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
            Previous
          </button>
          <span>Page {page} of {totalPages}</span>
          <button onClick={() => setPage(p => p + 1)} disabled={page === totalPages}>
            Next
          </button>
        </div>
      )}
    </div>
  );
}

SSR Search with Pagination

For SEO-friendly search result pages:

// routes/search.tsx
import { collectionSearch, useData, useSearchParams } from 'reroute-js/react';

const ssr = {
  async data({ searchParams }) {
    const page = Number.parseInt(searchParams?.get('page') || '1');
    return await collectionSearch({ 
      searchParams,
      page,
      pageSize: 20,
    });
  },
};

function SearchPage() {
  const [params] = useSearchParams();
  const query = params.get('q') || '';
  const data = useData();
  
  return (
    <div>
      <h1>Search Results for "{query}"</h1>
      {data && <p>Found {data.total} results (Page {data.page} of {data.totalPages})</p>}
      {data?.results.map(result => (
        <div key={result.id}>
          <a href={result.href}>{result.title}</a>
          <p>{result.excerpt}</p>
        </div>
      ))}
    </div>
  );
}

export default SearchPage;
export { ssr };

Visit /search?q=react&page=1 for server-rendered results.

📑 Table of Contents

Extract and display table of contents from markdown headings.

Basic Usage

// routes/blog/[slug].tsx
import { ContentRoute, Link, useToc, useRouter } from 'reroute-js/react';

function BlogPost() {
  return (
    <div style={{ display: 'flex', gap: '2rem' }}>
      <article style={{ flex: 1 }}>
        <ContentRoute collection="blog" />
      </article>
      <TableOfContents />
    </div>
  );
}

function TableOfContents() {
  const { toc } = useToc();  // Auto-detects collection and slug
  const { pathname } = useRouter();
  
  if (toc.length === 0) return null;

  return (
    <aside style={{ width: '250px', position: 'sticky', top: '2rem' }}>
      <h2>On This Page</h2>
      <nav>
        <ul>
          {toc.map((heading) => (
            <li 
              key={heading.slug}
              style={{ marginLeft: `${(heading.level - 1) * 0.75}rem` }}
            >
              <Link to={pathname} fragment={heading.slug}>
                {heading.text}
              </Link>
            </li>
          ))}
        </ul>
      </nav>
    </aside>
  );
}

export default BlogPost;

Enable Anchor Scrolling

Configure custom markdown components to add IDs to headings:

// reroute.config.ts
export default defineConfig({
  markdown: {
    components: './src/client/lib/markdown-components',
  },
});
// src/client/lib/markdown-components.tsx
import type { StreamdownProps } from 'streamdown';

function slugify(text: string): string {
  return text.toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-')
    .trim();
}

const markdownComponents: Components = {
  h2: ({ children, ...props }) => {
    const id = slugify(String(children));
    return <h2 id={id} {...props}>{children}</h2>;
  },
  h3: ({ children, ...props }) => {
    const id = slugify(String(children));
    return <h3 id={id} {...props}>{children}</h3>;
  },
  // ... h4, h5, h6
};

export default markdownComponents;

🎨 OG Image Generation

Auto-generate beautiful Open Graph (social media preview) images using React components.

Quick Start

1. Enable in config:

// reroute.config.ts
import { defineConfig } from 'reroute-js/core';

export default defineConfig({
  ogImage: {
    enabled: true,
    baseUrl: 'https://example.com',   // Required for social media sharing
    width: 1200,                      // Default: 1200
    height: 630,                      // Default: 630
    maxAge: 3600,                     // Cache duration in seconds
    avatar: 'https://example.com/logo.png',  // Optional
    siteName: 'My Site',                     // Optional
  },
});

2. Create OG templates:

// routes/blog/[og].tsx - OG template for all blog posts
import type { OGImageProps } from 'reroute-js/core';

export default function BlogOGImage({ meta, avatar }: OGImageProps) {
  return (
    <div style={{
      display: 'flex',
      flexDirection: 'column',
      width: '100%',
      height: '100%',
      background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
      padding: '80px',
    }}>
      <h1 style={{ fontSize: '72px', color: '#fff' }}>
        {meta.title}
      </h1>
      {avatar && (
        <img src={avatar} width={80} height={80} 
          style={{ borderRadius: '50%' }} />
      )}
    </div>
  );
}

File Structure

src/client/routes/
├── [og].tsx              # OG image for /
├── blog/
│   ├── index.tsx
│   ├── [og].tsx          # OG image for /blog
│   └── [slug].tsx        # Uses blog/[og].tsx
└── about/
    └── index.tsx         # Uses default template (no [og].tsx)

Available Props

interface OGImageProps {
  params: Record<string, string>;  // Route params { slug: 'my-post' }
  meta: any;                        // Frontmatter + page metadata
  avatar?: string;                  // Avatar URL (config or per-route)
  siteName?: string;                // Site name (config or per-route)
}

Styling

Use inline style prop with full CSS support:

<div style={{
  display: 'flex',
  background: 'linear-gradient(135deg, #667eea, #764ba2)',
  padding: '64px',
}}>
  <h1 style={{ fontSize: '72px', fontWeight: 'bold' }}>
    {meta.title}
  </h1>
</div>

Note: tw prop is NOT supported. Use inline styles with full CSS.

Per-Route Customization

Override avatar/siteName per route:

// routes/special-page.tsx
export const meta = {
  title: 'Special Page',
};

export const ssr = {
  ogImage: {
    avatar: 'https://example.com/special-avatar.png',
    siteName: 'Special Site',
  },
};

export default function SpecialPage() {
  return <div>Special content</div>;
}

CLI Preview

Preview OG images in your browser:

# Interactive list
reroute og

# Direct preview
reroute og /blog/my-post

URLs & Meta Tags

Generated URLs:

  • /https://example.com/__reroute_og/index.png
  • /bloghttps://example.com/__reroute_og/blog.png
  • /blog/my-posthttps://example.com/__reroute_og/blog/my-post.png

Auto-injected meta tags (when baseUrl is configured):

<meta property="og:image" content="https://example.com/__reroute_og/blog/my-post.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://example.com/__reroute_og/blog/my-post.png" />

Note: The baseUrl is required for proper social media sharing. Without it, relative URLs will be generated and a warning will be logged.

Default Template

If no [og].tsx exists for a route, uses built-in template with:

  • Title from meta
  • Description from meta
  • Avatar and siteName from config
  • Clean black card design

📊 OpenTelemetry

Server Telemetry

Track HTTP requests, errors, and system metrics.

Installation:

# Server telemetry packages
bun add source-map \
  @opentelemetry/api \
  @opentelemetry/api-logs \
  @opentelemetry/exporter-logs-otlp-proto \
  @opentelemetry/exporter-metrics-otlp-proto \
  @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/resources \
  @opentelemetry/sdk-logs \
  @opentelemetry/sdk-metrics \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/semantic-conventions \
  

Configuration in reroute.config.ts:

export default defineConfig({
  telemetry: {
    // Server telemetry
    enabled: true,
    environment: process.env.NODE_ENV || 'development',
    ignoreRoutes: [
      '/health',
      /^\/assets\//,  // Use regex instead of functions (closures don't serialize to browser)
      /\.(js|css|png|jpg|svg|ico)$/,
    ],
    sampleRate: 1.0,
    capture: [
      ...RerouteHeaders.http(),
      ...RerouteHeaders.request(),
      ...CloudflareHeaders.geo(),
    ],
    
    // Browser telemetry proxy (avoids CORS and ad blockers)
    proxy: {
      enabled: true,                    // Default
      pathname: '/__reroute_telemetry', // Default
      verbose: false,                   // Log proxy requests
    },
    
    // Browser telemetry
    browser: {
      enabled: true,
      serviceName: 'my-app-browser',
      environment: process.env.NODE_ENV || 'development',
      otlpEndpoint: '/__reroute_telemetry',  // Must match proxy.pathname
      enableConsoleCapture: true,
    },
  },
});

Usage - Config loaded automatically:

import { telemetry } from 'reroute-js/telemetry/server';

new Elysia()
  .use(telemetry())
  .use(reroute())
  .listen(3000);

Browser telemetry - Track client-side events and errors.

Installation:

# Browser telemetry packages
bun add @opentelemetry/api \
  @opentelemetry/auto-instrumentations-web \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/instrumentation \
  @opentelemetry/resources \
  @opentelemetry/sdk-trace-base \
  @opentelemetry/sdk-trace-web \
  @opentelemetry/semantic-conventions

Usage:

import { TelemetryProvider } from 'reroute-js/telemetry/react/react';

// Auto-loads from telemetry.browser config
<TelemetryProvider>
  <RerouteProvider {...bundle} />
</TelemetryProvider>

// Or override config values
<TelemetryProvider enabled={true} serviceName="custom-name">
  <RerouteProvider {...bundle} />
</TelemetryProvider>

Custom Instrumentation

Create custom spans for API calls, database queries, or any operation:

import { withSpan } from 'reroute-js/telemetry/server';

// Instrument API endpoints
app.get('/api/products', async () => {
  return withSpan('api.get_products', async (span) => {
    span.setAttribute('api.operation', 'list_products');
    
    const products = await fetchProducts();
    span.setAttribute('api.result_count', products.length);
    
    return products;
  });
});

// Nested spans automatically create parent-child relationships
app.get('/api/orders', async () => {
  return withSpan('api.get_orders', async (span) => {
    span.setAttribute('api.operation', 'list_orders');
    
    // Child span
    const user = await withSpan('api.fetch_user', async (childSpan) => {
      childSpan.setAttribute('user.id', userId);
      return await getUser(userId);
    });
    
    return { user, orders: await getOrders(user.id) };
  });
});

Error Handling:

Errors are automatically tracked and the span is properly closed:

app.get('/api/user/:id', async ({ params }) => {
  return withSpan('api.get_user', async (span) => {
    span.setAttribute('user.id', params.id);
    
    const user = await getUser(params.id);
    
    if (!user) {
      // Error is automatically captured in the span
      throw new Error('User not found');
    }
    
    return user;
  });
});

When an error occurs inside a span:

  1. Error is recorded - span.recordException() captures the full error details
  2. Status is set - Span status automatically set to ERROR with error message
  3. Span is closed - Span properly ended even when errors occur
  4. Error is re-thrown - Error propagates normally (not swallowed)

This means errors appear in your traces with full context while maintaining normal error handling behavior.

Key Features:

  • Automatic error tracking - Errors are captured and recorded in spans
  • Nested spans - Child spans automatically linked to parent context
  • Zero overhead when disabled - No-op implementation when OpenTelemetry not installed

Reroute SSR Tracing

Built-in tracing for debugging SSR, 404s, and cache behavior. Automatically traces SSR rendering, 404s, data loading, and errors. Filter by reroute.* attributes in SigNoz/Jaeger.

📖 Learn More

🏗️ Build & Deployment

Production Build

# Build JavaScript bundle
reroute build

# Build standalone binary
reroute build --compile

# Custom output name
reroute build --compile -o myapp

What Gets Generated?

When you run reroute build, two directories are created:

  • dist/ - Your production server bundle or compiled binary
  • .reroute/ - Runtime assets needed by the server (client bundles, content metadata)

Key points:

  • ✅ No code duplication (server vs browser targets)
  • ✅ Production-ready compression (Brotli + Gzip)
  • ✅ Code splitting enabled (lazy-loaded chunks)
  • ✅ Only ~58KB JavaScript transferred to browsers

Deployment Checklist

For compiled binary (--compile):

dist/app              # Your executable
.reroute/
  ├── bundles/        # Client-side code
  ├── collections/    # Content metadata
  └── content.ts      # Content registry for SSR

For JavaScript bundle (no --compile):

dist/app.js           # Server bundle
.reroute/             # Runtime assets (same as above)
node_modules/         # Production dependencies

Learn More

See BUILD.md for detailed documentation on:

  • What's in each folder
  • How the build process works
  • Runtime architecture
  • Optimization tips
  • Troubleshooting

📝 License

MIT