reroute-js 0.11.5 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -5
- package/_/basic/package.json +7 -6
- package/_/basic/reroute.config.ts +0 -5
- package/_/basic/src/index.ts +5 -2
- package/_/blog/package.json +7 -6
- package/_/blog/src/index.ts +2 -2
- package/_/redirects/package.json +7 -6
- package/_/rss/package.json +28 -0
- package/_/rss/reroute.config.ts +23 -0
- package/_/rss/tsconfig.json +25 -0
- package/_/sitemap/package.json +28 -0
- package/_/sitemap/reroute.config.ts +23 -0
- package/_/sitemap/tsconfig.json +25 -0
- package/_/store/package.json +7 -6
- package/_/store/src/index.ts +3 -6
- package/_/streaming/package.json +7 -6
- package/cli/bin.d.ts +1 -1
- package/cli/bin.js +893 -53
- package/cli/bin.js.map +17 -11
- package/cli/index.d.ts +1 -1
- package/cli/index.js +4 -4
- package/cli/index.js.map +1 -1
- package/cli/src/cli.d.ts +1 -1
- package/cli/src/commands/analyze.d.ts +1 -1
- package/cli/src/commands/build.d.ts +1 -1
- package/cli/src/commands/dev.d.ts +1 -1
- package/cli/src/commands/gen.d.ts +1 -1
- package/cli/src/commands/index.d.ts +1 -1
- package/cli/src/commands/init.d.ts +1 -1
- package/cli/src/commands/lib/command.d.ts +1 -1
- package/cli/src/commands/lib/env.d.ts +1 -1
- package/cli/src/commands/lib/index.d.ts +1 -1
- package/cli/src/commands/lib/log.d.ts +1 -1
- package/cli/src/commands/lib/markdown/availability.d.ts +1 -1
- package/cli/src/commands/lib/markdown/index.d.ts +1 -1
- package/cli/src/commands/lib/markdown/processor.d.ts +1 -1
- package/cli/src/commands/lib/production.d.ts +1 -1
- package/cli/src/commands/lib/server.d.ts +1 -1
- package/cli/src/commands/lib/streaming/analyzer.d.ts +1 -1
- package/cli/src/commands/lib/streaming/suspense.d.ts +1 -1
- package/cli/src/commands/lib/tailwind.d.ts +1 -1
- package/cli/src/commands/lib/version.d.ts +1 -1
- package/cli/src/commands/start.d.ts +1 -1
- package/cli/src/index.d.ts +1 -1
- package/core/index.d.ts +1 -1
- package/core/index.js +902 -67
- package/core/index.js.map +16 -11
- package/core/src/bundler/hash.d.ts +1 -1
- package/core/src/bundler/index.d.ts +1 -1
- package/core/src/config.d.ts +86 -2
- package/core/src/config.d.ts.map +1 -1
- package/core/src/content/discovery.d.ts +1 -1
- package/core/src/content/index.d.ts +1 -1
- package/core/src/content/metadata.d.ts +1 -1
- package/core/src/index.d.ts +3 -1
- package/core/src/index.d.ts.map +1 -1
- package/core/src/rss/discovery.d.ts +43 -0
- package/core/src/rss/discovery.d.ts.map +1 -0
- package/core/src/rss/generator.d.ts +29 -0
- package/core/src/rss/generator.d.ts.map +1 -0
- package/core/src/rss/index.d.ts +12 -0
- package/core/src/rss/index.d.ts.map +1 -0
- package/core/src/sitemap/discovery.d.ts +51 -0
- package/core/src/sitemap/discovery.d.ts.map +1 -0
- package/core/src/sitemap/generator.d.ts +42 -0
- package/core/src/sitemap/generator.d.ts.map +1 -0
- package/core/src/sitemap/index.d.ts +12 -0
- package/core/src/sitemap/index.d.ts.map +1 -0
- package/core/src/ssr/index.d.ts +1 -1
- package/core/src/ssr/lib/cache.d.ts +3 -2
- package/core/src/ssr/lib/cache.d.ts.map +1 -1
- package/core/src/ssr/lib/collections.d.ts +1 -1
- package/core/src/ssr/lib/compression.d.ts +1 -1
- package/core/src/ssr/lib/compute.d.ts +4 -1
- package/core/src/ssr/lib/compute.d.ts.map +1 -1
- package/core/src/ssr/lib/data.d.ts +1 -1
- package/core/src/ssr/lib/data.d.ts.map +1 -1
- package/core/src/ssr/lib/html.d.ts +1 -1
- package/core/src/ssr/lib/index.d.ts +1 -1
- package/core/src/ssr/lib/metadata.d.ts +1 -1
- package/core/src/ssr/lib/mime.d.ts +1 -1
- package/core/src/ssr/lib/modules.d.ts +1 -1
- package/core/src/ssr/lib/path.d.ts +1 -1
- package/core/src/ssr/lib/preload.d.ts +1 -1
- package/core/src/ssr/lib/scripts.d.ts +5 -1
- package/core/src/ssr/lib/scripts.d.ts.map +1 -1
- package/core/src/ssr/lib/seed.d.ts +1 -1
- package/core/src/ssr/lib/styles.d.ts +1 -1
- package/core/src/ssr/lib/template.d.ts +1 -1
- package/core/src/ssr/lib/types.d.ts +1 -1
- package/core/src/ssr/render.d.ts +1 -1
- package/core/src/ssr/render.d.ts.map +1 -1
- package/core/src/ssr/stream.d.ts +1 -1
- package/core/src/ssr/stream.d.ts.map +1 -1
- package/elysia/index.d.ts +1 -1
- package/elysia/index.js +1265 -59
- package/elysia/index.js.map +20 -12
- package/elysia/src/index.d.ts +1 -1
- package/elysia/src/libs/cache.d.ts +1 -1
- package/elysia/src/libs/http.d.ts +1 -1
- package/elysia/src/libs/image.d.ts +1 -1
- package/elysia/src/plugin.d.ts +1 -1
- package/elysia/src/plugin.d.ts.map +1 -1
- package/elysia/src/routes/artifacts.d.ts +1 -1
- package/elysia/src/routes/content.d.ts +1 -1
- package/elysia/src/routes/image.d.ts +1 -1
- package/elysia/src/routes/internal.d.ts +1 -1
- package/elysia/src/routes/redirects.d.ts +1 -1
- package/elysia/src/routes/rss.d.ts +27 -0
- package/elysia/src/routes/rss.d.ts.map +1 -0
- package/elysia/src/routes/sitemap.d.ts +27 -0
- package/elysia/src/routes/sitemap.d.ts.map +1 -0
- package/elysia/src/routes/ssr.d.ts +1 -1
- package/elysia/src/routes/static.d.ts +1 -1
- package/elysia/src/types.d.ts +6 -1
- package/elysia/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/react/index.d.ts +1 -1
- package/react/index.js +78 -42
- package/react/index.js.map +8 -7
- package/react/src/components/ClientOnly.d.ts +22 -3
- package/react/src/components/ClientOnly.d.ts.map +1 -1
- package/react/src/components/ContentRoute.d.ts +1 -1
- package/react/src/components/Image.d.ts +1 -1
- package/react/src/components/LazyRoute.d.ts +1 -1
- package/react/src/components/Link.d.ts +1 -1
- package/react/src/components/Link.d.ts.map +1 -1
- package/react/src/components/Markdown.d.ts +1 -1
- package/react/src/components/Outlet.d.ts +1 -1
- package/react/src/components/index.d.ts +1 -1
- package/react/src/hooks/index.d.ts +2 -1
- package/react/src/hooks/index.d.ts.map +1 -1
- package/react/src/hooks/useContent.d.ts +10 -10
- package/react/src/hooks/useContent.d.ts.map +1 -1
- package/react/src/hooks/useData.d.ts +1 -1
- package/react/src/hooks/useFeed.d.ts +49 -0
- package/react/src/hooks/useFeed.d.ts.map +1 -0
- package/react/src/hooks/useLayoutData.d.ts +1 -1
- package/react/src/hooks/useNavigate.d.ts +1 -1
- package/react/src/hooks/useParams.d.ts +1 -1
- package/react/src/hooks/useRouter.d.ts +1 -1
- package/react/src/hooks/useSearchParams.d.ts +1 -1
- package/react/src/index.d.ts +1 -1
- package/react/src/lib/content.d.ts +1 -1
- package/react/src/lib/head.d.ts +1 -1
- package/react/src/lib/index.d.ts +1 -1
- package/react/src/lib/lazy-route.d.ts +1 -1
- package/react/src/lib/route-loader.d.ts +1 -1
- package/react/src/providers/ContentProvider.d.ts +1 -1
- package/react/src/providers/RerouteProvider.d.ts +1 -1
- package/react/src/providers/RouterProvider.d.ts +1 -1
- package/react/src/providers/RouterProvider.d.ts.map +1 -1
- package/react/src/providers/index.d.ts +1 -1
- package/react/src/types/any.d.ts +1 -1
- package/react/src/types/index.d.ts +1 -1
- package/react/src/types/router.d.ts +1 -1
- package/_/redirects/README.md +0 -63
package/README.md
CHANGED
|
@@ -45,15 +45,17 @@ Reroute is a dead-simple file-based routing framework for building full-stack Re
|
|
|
45
45
|
- `useRouter()` - Access router state and utilities
|
|
46
46
|
- 📚 **Content Hooks**:
|
|
47
47
|
- `useContent()` - Load and manage content collections with sorting
|
|
48
|
+
- `useFeed()` - Discover RSS feed URLs for current route
|
|
48
49
|
- 🧩 **Components**:
|
|
49
50
|
- `<Link>` - Client-side navigation with automatic prefetching
|
|
50
51
|
- `<Image>` - Optimized images with automatic format negotiation and lazy loading
|
|
51
52
|
- `<Outlet>` - Render nested route components
|
|
52
53
|
- `<ContentRoute>` - Dynamic content rendering with metadata injection
|
|
54
|
+
- `<ClientOnly>` - Client-side only rendering with optional layout preservation (className/style props)
|
|
53
55
|
- 🎭 **Providers**:
|
|
54
|
-
- `<
|
|
55
|
-
- `<
|
|
56
|
-
- `<
|
|
56
|
+
- `<RerouteProvider>` - **USE THIS** - All-in-one provider with routing + content + artifacts
|
|
57
|
+
- `<RouterProvider>` - Low-level router only (internal use, prefer RerouteProvider)
|
|
58
|
+
- `<ContentProvider>` - Content system context (internal use)
|
|
57
59
|
- 🗂️ **SSR Data**:
|
|
58
60
|
- `useData()` - Read route-level SSR data without loaders
|
|
59
61
|
- `export const ssr = { data() {} }` - Route data function executed on server
|
|
@@ -116,6 +118,23 @@ Reroute is a dead-simple file-based routing framework for building full-stack Re
|
|
|
116
118
|
- 🌍 **Language Attribute** - Dynamic `lang` attribute support
|
|
117
119
|
- 🎯 **SEO Optimization** - Title, description, and custom meta tags
|
|
118
120
|
|
|
121
|
+
### 🗺️ Sitemap Generation
|
|
122
|
+
- 🔍 **Auto-Discovery** - Automatically discovers static routes, content collections, and ssr.data routes
|
|
123
|
+
- 📄 **XML Generation** - Standards-compliant sitemap.xml following Google's specifications
|
|
124
|
+
- 💾 **Smart Caching** - Generated on first request and cached according to maxAge
|
|
125
|
+
- 📊 **Auto-Pagination** - Automatically splits into multiple files for sites with 50k+ URLs
|
|
126
|
+
- 🎨 **Custom Extractors** - Content-agnostic URL and date extraction via configuration
|
|
127
|
+
- 📅 **Metadata Support** - Includes lastmod, changefreq, and priority for each URL
|
|
128
|
+
|
|
129
|
+
### 📰 RSS Feed Generation
|
|
130
|
+
- 🔍 **Auto-Discovery** - Discovers content collections, route ssr.data, and layout ssr.data
|
|
131
|
+
- 📄 **RSS & Atom Support** - Standards-compliant RSS 2.0 and Atom 1.0 formats
|
|
132
|
+
- 📚 **Per-Collection Feeds** - Each collection gets its own feed (e.g., /blog/feed.xml)
|
|
133
|
+
- 💾 **Smart Caching** - Generated on first request and cached according to maxAge
|
|
134
|
+
- 🎨 **Custom Extractors** - Content-agnostic extraction for title, description, author, pubDate
|
|
135
|
+
- 🔄 **Auto-Sorting** - Items sorted by publication date (newest first)
|
|
136
|
+
- 📏 **Limit Control** - Configurable maximum items per feed (default: 50)
|
|
137
|
+
|
|
119
138
|
### 📦 Configuration Options
|
|
120
139
|
- 🎯 **Custom Assets Directory** - Configure where your client files live
|
|
121
140
|
- 🔧 **URL Prefix** - Deploy to subdirectories with custom prefixes
|
|
@@ -152,10 +171,19 @@ import { useData, useParams } from 'reroute-js/react';
|
|
|
152
171
|
import { getProduct } from '../lib/api';
|
|
153
172
|
|
|
154
173
|
export const ssr = {
|
|
155
|
-
async data({ params }: { params: { id?: string } }) {
|
|
174
|
+
async data({ params, set }: { params: { id?: string }; set: { status: number } }) {
|
|
156
175
|
const id = Number.parseInt(params.id || '');
|
|
157
|
-
if (!Number.isFinite(id))
|
|
176
|
+
if (!Number.isFinite(id)) {
|
|
177
|
+
set.status = 404; // Set HTTP status code
|
|
178
|
+
return { product: null };
|
|
179
|
+
}
|
|
180
|
+
|
|
158
181
|
const product = await getProduct(id);
|
|
182
|
+
if (!product) {
|
|
183
|
+
set.status = 404;
|
|
184
|
+
return { product: null };
|
|
185
|
+
}
|
|
186
|
+
|
|
159
187
|
return { product };
|
|
160
188
|
},
|
|
161
189
|
};
|
|
@@ -171,6 +199,47 @@ Notes:
|
|
|
171
199
|
- Works with Bun + Elysia, no client loaders needed for initial render
|
|
172
200
|
- Data is injected as `window.__REROUTE_DATA__` and read during hydration
|
|
173
201
|
- Use with existing content features (useContent); both can be seeded in the same page
|
|
202
|
+
- **HTTP Status Control:** Use `set.status` to set response status codes (follows Elysia's API). Non-200 responses are not cached
|
|
203
|
+
|
|
204
|
+
## 🎭 Client-Only Rendering
|
|
205
|
+
|
|
206
|
+
The `ClientOnly` component prevents hydration mismatches for browser-specific code and supports layout preservation to prevent flickering:
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
import { ClientOnly } from 'reroute-js/react';
|
|
210
|
+
|
|
211
|
+
// Basic usage - prevent hydration mismatch
|
|
212
|
+
<ClientOnly fallback={<div>Loading...</div>}>
|
|
213
|
+
<CountdownTimer />
|
|
214
|
+
</ClientOnly>
|
|
215
|
+
|
|
216
|
+
// Prevent layout shift with height preservation
|
|
217
|
+
<ClientOnly
|
|
218
|
+
fallback={<PricingSkeleton />}
|
|
219
|
+
className="min-h-screen"
|
|
220
|
+
>
|
|
221
|
+
<PricingSection />
|
|
222
|
+
</ClientOnly>
|
|
223
|
+
|
|
224
|
+
// With inline styles
|
|
225
|
+
<ClientOnly
|
|
226
|
+
fallback={<Loader />}
|
|
227
|
+
style={{ minHeight: '600px' }}
|
|
228
|
+
>
|
|
229
|
+
<DashboardContent />
|
|
230
|
+
</ClientOnly>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**When to use:**
|
|
234
|
+
- Components using browser APIs (`window`, `localStorage`, `navigator`)
|
|
235
|
+
- Dynamic values (`Date.now()`, `Math.random()`)
|
|
236
|
+
- Time-sensitive data that differs between server and client
|
|
237
|
+
- Large lazy-loaded components (use `className`/`style` to preserve height)
|
|
238
|
+
|
|
239
|
+
**Props:**
|
|
240
|
+
- `fallback` - Content to show during SSR and before hydration
|
|
241
|
+
- `className` - Optional className for wrapper div (useful for layout preservation)
|
|
242
|
+
- `style` - Optional inline styles for wrapper div (useful for layout preservation)
|
|
174
243
|
|
|
175
244
|
## 🔐 Environment Variables
|
|
176
245
|
|
|
@@ -391,6 +460,210 @@ export default function MyPage() {
|
|
|
391
460
|
- **Optional**: Only bundle dependencies you actually use
|
|
392
461
|
|
|
393
462
|
|
|
463
|
+
## 🗺️ Sitemap & RSS Feed Generation
|
|
464
|
+
|
|
465
|
+
Reroute provides automatic sitemap and RSS feed generation with zero configuration required.
|
|
466
|
+
|
|
467
|
+
### Sitemap Generation
|
|
468
|
+
|
|
469
|
+
Generate standards-compliant XML sitemaps for search engines.
|
|
470
|
+
|
|
471
|
+
#### Quick Start
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
// reroute.config.ts
|
|
475
|
+
import { defineConfig } from 'reroute-js/core';
|
|
476
|
+
|
|
477
|
+
export default defineConfig({
|
|
478
|
+
sitemap: {
|
|
479
|
+
enabled: true, // opt-in (default: false)
|
|
480
|
+
baseUrl: 'https://your-domain.com',
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Your sitemap is now available at `/sitemap.xml`!
|
|
486
|
+
|
|
487
|
+
#### What Gets Included
|
|
488
|
+
|
|
489
|
+
All three sources are automatically discovered:
|
|
490
|
+
|
|
491
|
+
1. **Static Routes** - Pages in `routes/` folder (e.g., `/`, `/about`, `/contact`)
|
|
492
|
+
2. **Content Collections** - Items in `routes/[collection]/content/` with metadata
|
|
493
|
+
3. **Route SSR Data** - Dynamic data from route `ssr.data` exports
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
#### Custom Configuration
|
|
498
|
+
|
|
499
|
+
For non-standard data structures:
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
sitemap: {
|
|
503
|
+
enabled: true,
|
|
504
|
+
baseUrl: 'https://your-domain.com',
|
|
505
|
+
|
|
506
|
+
// Extract URL segment from ssr.data items
|
|
507
|
+
extractUrl: (item, routePattern) => {
|
|
508
|
+
// Products use SKU instead of slug
|
|
509
|
+
if (routePattern === '/products' && item.sku) {
|
|
510
|
+
return item.sku; // /products/laptop-pro-2024
|
|
511
|
+
}
|
|
512
|
+
return null; // fall back to defaults (slug, id, name, key)
|
|
513
|
+
},
|
|
514
|
+
|
|
515
|
+
// Extract lastmod date
|
|
516
|
+
extractLastmod: (item) => item.updatedAt || item.date,
|
|
517
|
+
|
|
518
|
+
// Default settings
|
|
519
|
+
changefreq: 'weekly', // or 'daily', 'monthly', etc.
|
|
520
|
+
priority: 0.5, // 0.0 to 1.0
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
#### Features
|
|
525
|
+
|
|
526
|
+
- **Runtime generation** - Generated on first request, cached according to `maxAge`
|
|
527
|
+
- **Auto-pagination** - Sites with 50k+ URLs automatically split into multiple files with sitemap index
|
|
528
|
+
- **SEO-friendly** - Includes `<lastmod>`, `<changefreq>`, and `<priority>` tags
|
|
529
|
+
- **Content-agnostic** - Works with any data structure via custom extractors
|
|
530
|
+
- **Smart caching** - Respects your `maxAge` configuration
|
|
531
|
+
|
|
532
|
+
### RSS Feed Generation
|
|
533
|
+
|
|
534
|
+
Generate RSS 2.0 and Atom 1.0 feeds for content syndication.
|
|
535
|
+
|
|
536
|
+
#### Quick Start
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
// reroute.config.ts
|
|
540
|
+
import { defineConfig } from 'reroute-js/core';
|
|
541
|
+
|
|
542
|
+
export default defineConfig({
|
|
543
|
+
rss: {
|
|
544
|
+
enabled: true, // opt-in (default: false)
|
|
545
|
+
baseUrl: 'https://your-domain.com',
|
|
546
|
+
title: 'My Blog',
|
|
547
|
+
description: 'Latest posts and updates',
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Your feeds are automatically available at:
|
|
553
|
+
- `/feed.xml` - Main feed (combines all sources below)
|
|
554
|
+
- `/blog/feed.xml` - Blog collection (from `routes/blog/content/`)
|
|
555
|
+
- `/changelog/feed.xml` - Changelog route (from route's `ssr.data`)
|
|
556
|
+
- `/announcements/feed.xml` - Announcements (from layout's `ssr.data`)
|
|
557
|
+
|
|
558
|
+
Pattern:
|
|
559
|
+
- `/[collection]/feed.xml` - For any content collection
|
|
560
|
+
- `/[route]/feed.xml` - For any route/layout with `ssr.data`
|
|
561
|
+
|
|
562
|
+
#### What Gets Included
|
|
563
|
+
|
|
564
|
+
All three sources are automatically discovered:
|
|
565
|
+
|
|
566
|
+
1. **Content Collections** - Items from `routes/[collection]/content/` folders
|
|
567
|
+
- Example: Blog posts with metadata (title, description, date)
|
|
568
|
+
|
|
569
|
+
2. **Route SSR Data** - Arrays returned by route `ssr.data` functions
|
|
570
|
+
- Example: Changelog versions fetched from API
|
|
571
|
+
- Must export `ssr.data` that returns object containing arrays
|
|
572
|
+
|
|
573
|
+
3. **Layout SSR Data** - Arrays returned by layout `ssr.data` functions
|
|
574
|
+
- Example: Site-wide announcements fetched in layout
|
|
575
|
+
- Must export `ssr.data` in `[layout].tsx` that returns object containing arrays
|
|
576
|
+
|
|
577
|
+
**Important:** For ssr.data discovery, the data must contain arrays. Nested arrays (e.g., `{ user: { notifications: [...] } }`) are automatically found via recursive discovery.
|
|
578
|
+
|
|
579
|
+
#### Custom Configuration
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
rss: {
|
|
583
|
+
enabled: true,
|
|
584
|
+
baseUrl: 'https://your-domain.com',
|
|
585
|
+
title: 'My Blog',
|
|
586
|
+
description: 'Latest updates',
|
|
587
|
+
limit: 50, // max items per feed (default: 50)
|
|
588
|
+
format: 'rss', // 'rss' (RSS 2.0) or 'atom' (Atom 1.0)
|
|
589
|
+
|
|
590
|
+
// Extract URL segment from ssr.data items
|
|
591
|
+
extractUrl: (item, routePattern) => {
|
|
592
|
+
// Changelog uses version field
|
|
593
|
+
if (routePattern === '/changelog' && item.version) {
|
|
594
|
+
return item.version; // /changelog/1.2.0
|
|
595
|
+
}
|
|
596
|
+
return null; // fall back to defaults (slug, id, name, key)
|
|
597
|
+
},
|
|
598
|
+
|
|
599
|
+
// Extract publication date
|
|
600
|
+
extractPubDate: (item) => item.publishedAt || item.date || item.createdAt,
|
|
601
|
+
|
|
602
|
+
// Extract author
|
|
603
|
+
extractAuthor: (item) => item.author || item.authorName,
|
|
604
|
+
|
|
605
|
+
// Extract full content (optional, for full-text feeds)
|
|
606
|
+
extractContent: (item) => item.body || item.content,
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
#### Features
|
|
611
|
+
|
|
612
|
+
- **Multi-source** - Combines content collections, route ssr.data, and layout ssr.data
|
|
613
|
+
- **Per-collection feeds** - Each collection gets `/[collection]/feed.xml`
|
|
614
|
+
- **Per-route feeds** - Routes with ssr.data get `/[route]/feed.xml`
|
|
615
|
+
- **RSS & Atom** - Choose RSS 2.0 or Atom 1.0 format
|
|
616
|
+
- **Auto-sorting** - Items sorted by pubDate (newest first)
|
|
617
|
+
- **Smart caching** - Generated on first request, cached according to `maxAge`
|
|
618
|
+
- **Recursive discovery** - Finds arrays even in nested data (e.g., `user.notifications`)
|
|
619
|
+
- **Content-agnostic** - Works with any data structure via custom extractors
|
|
620
|
+
|
|
621
|
+
#### Discovering Feeds in Your UI
|
|
622
|
+
|
|
623
|
+
Use the `useFeed()` hook to automatically discover and link to RSS feeds:
|
|
624
|
+
|
|
625
|
+
```tsx
|
|
626
|
+
import { useFeed } from 'reroute-js/react';
|
|
627
|
+
|
|
628
|
+
function Header() {
|
|
629
|
+
const feed = useFeed();
|
|
630
|
+
|
|
631
|
+
return (
|
|
632
|
+
<header>
|
|
633
|
+
{/* Show collection/route-specific feed if available */}
|
|
634
|
+
{feed.hasFeed && (
|
|
635
|
+
<a href={feed.feedUrl!} target="_blank">
|
|
636
|
+
Subscribe to {feed.collection}
|
|
637
|
+
</a>
|
|
638
|
+
)}
|
|
639
|
+
|
|
640
|
+
{/* Main feed always available */}
|
|
641
|
+
<a href={feed.mainFeedUrl}>RSS Feed</a>
|
|
642
|
+
</header>
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
- `feedUrl` - URL of the specific feed (`/blog/feed.xml`, `/changelog/feed.xml`, etc.) or `null`
|
|
649
|
+
- `mainFeedUrl` - Main feed URL (always `/feed.xml`)
|
|
650
|
+
- `collection` - Collection/route name (`'blog'`, `'changelog'`, etc.) or `null`
|
|
651
|
+
- `hasFeed` - Boolean indicating if current route has a specific feed
|
|
652
|
+
|
|
653
|
+
#### SPA Navigation Support
|
|
654
|
+
|
|
655
|
+
**Important:** When using layout `ssr.data`, use `<Link>` components (not `<a>` tags) for proper client-side navigation:
|
|
656
|
+
|
|
657
|
+
```tsx
|
|
658
|
+
import { Link } from 'reroute-js/react';
|
|
659
|
+
|
|
660
|
+
// ✅ Good - prefetches layout data on hover
|
|
661
|
+
<Link to="/announcements">Announcements</Link>
|
|
662
|
+
|
|
663
|
+
// ❌ Bad - full page reload, no prefetch
|
|
664
|
+
<a href="/announcements">Announcements</a>
|
|
665
|
+
```
|
|
666
|
+
|
|
394
667
|
## 📖 Learn More
|
|
395
668
|
|
|
396
669
|
- [Examples](./examples) - Example projects to get started
|
package/_/basic/package.json
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
"description": "A Reroute application",
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"scripts": {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
"types": "tsc --noEmit",
|
|
7
|
+
"gen": "reroute gen",
|
|
8
|
+
"start": "reroute start --prod",
|
|
9
|
+
"dev": "reroute dev",
|
|
10
|
+
"build": "reroute build",
|
|
11
|
+
"kill": "bunx kill-port 3000",
|
|
12
|
+
"analyze": "reroute analyze --prod --open"
|
|
12
13
|
},
|
|
13
14
|
"private": true,
|
|
14
15
|
"dependencies": {
|
package/_/basic/src/index.ts
CHANGED
|
@@ -5,7 +5,10 @@ const app = new Elysia()
|
|
|
5
5
|
.use(
|
|
6
6
|
reroute(),
|
|
7
7
|
)
|
|
8
|
-
.get('/api/message', () =>
|
|
8
|
+
.get('/api/message', () => {
|
|
9
|
+
console.log('[api] log from my api')
|
|
10
|
+
return { message: 'Hello from server' }
|
|
11
|
+
})
|
|
9
12
|
.listen(Number(Bun.env.PORT || '3000'));
|
|
10
13
|
|
|
11
|
-
console.log(`[
|
|
14
|
+
console.log(`[app] Running at ${app.server?.hostname}:${app.server?.port}`);
|
package/_/blog/package.json
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
"description": "A Reroute application",
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"scripts": {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
"types": "tsc --noEmit",
|
|
7
|
+
"gen": "reroute gen",
|
|
8
|
+
"start": "PORT=3001 reroute start --prod",
|
|
9
|
+
"dev": "PORT=3001 reroute dev",
|
|
10
|
+
"build": "reroute build",
|
|
11
|
+
"kill": "bunx kill-port 3001",
|
|
12
|
+
"analyze": "reroute analyze --prod --open"
|
|
12
13
|
},
|
|
13
14
|
"private": true,
|
|
14
15
|
"dependencies": {
|
package/_/blog/src/index.ts
CHANGED
|
@@ -9,8 +9,8 @@ const app = new Elysia()
|
|
|
9
9
|
console.log('[api] log from my api');
|
|
10
10
|
return { message: 'Hello from server' };
|
|
11
11
|
})
|
|
12
|
-
.listen(Number(Bun.env.PORT || '
|
|
12
|
+
.listen(Number(Bun.env.PORT || '3001'));
|
|
13
13
|
|
|
14
14
|
console.log(
|
|
15
|
-
`[
|
|
15
|
+
`[app] Blog running at ${app.server?.hostname}:${app.server?.port} on ${Bun.env.NODE_ENV || 'development'}`,
|
|
16
16
|
);
|
package/_/redirects/package.json
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
"description": "A Reroute application",
|
|
5
5
|
"private": true,
|
|
6
6
|
"scripts": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
"types": "tsc --noEmit",
|
|
8
|
+
"gen": "reroute gen",
|
|
9
|
+
"start": "PORT=3002 reroute start --prod",
|
|
10
|
+
"dev": "PORT=3002 reroute dev",
|
|
11
|
+
"build": "reroute build",
|
|
12
|
+
"kill": "bunx kill-port 3002",
|
|
13
|
+
"analyze": "reroute analyze --prod --open"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
16
|
"reroute-js": "latest",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A Reroute application",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"types": "tsc --noEmit",
|
|
8
|
+
"gen": "reroute gen",
|
|
9
|
+
"start": "PORT=3003 reroute start --prod",
|
|
10
|
+
"dev": "PORT=3003 reroute dev",
|
|
11
|
+
"build": "reroute build",
|
|
12
|
+
"kill": "bunx kill-port 3003",
|
|
13
|
+
"analyze": "reroute analyze --prod --open"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"reroute-js": "latest",
|
|
17
|
+
"elysia": "^1.4.12",
|
|
18
|
+
"react": "^19.2.0",
|
|
19
|
+
"react-dom": "^19.2.0",
|
|
20
|
+
"sharp": "^0.34.4"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/bun": "latest",
|
|
24
|
+
"@types/react": "^19.2.2",
|
|
25
|
+
"@types/react-dom": "^19.2.2",
|
|
26
|
+
"typescript": "^5"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from 'reroute-js/core';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
options: {
|
|
5
|
+
app: './src/client/App',
|
|
6
|
+
maxAge: 3600,
|
|
7
|
+
compression: true,
|
|
8
|
+
},
|
|
9
|
+
sitemap: {
|
|
10
|
+
enabled: true,
|
|
11
|
+
baseUrl: 'http://localhost:3003',
|
|
12
|
+
// Test pagination with small page size (default 50k)
|
|
13
|
+
// maxUrlsPerPage: 5,
|
|
14
|
+
// Custom URL extractor - products use SKU instead of default fields
|
|
15
|
+
extractUrl: (item, routePattern) => {
|
|
16
|
+
if (routePattern === '/products' && item.sku) {
|
|
17
|
+
return item.sku;
|
|
18
|
+
}
|
|
19
|
+
return null; // fall back to defaults (slug, id, name, etc.)
|
|
20
|
+
},
|
|
21
|
+
extractLastmod: (item) => item.updatedAt || item.date,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"baseUrl": ".",
|
|
11
|
+
"rootDir": ".",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"outDir": "dist/types",
|
|
15
|
+
"emitDeclarationOnly": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"jsx": "react-jsx",
|
|
21
|
+
"types": ["bun", "node"]
|
|
22
|
+
},
|
|
23
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
24
|
+
"exclude": ["node_modules", "dist"]
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A Reroute application",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"types": "tsc --noEmit",
|
|
8
|
+
"gen": "reroute gen",
|
|
9
|
+
"start": "PORT=3004 reroute start --prod",
|
|
10
|
+
"dev": "PORT=3004 reroute dev",
|
|
11
|
+
"build": "reroute build",
|
|
12
|
+
"kill": "bunx kill-port 3004",
|
|
13
|
+
"analyze": "reroute analyze --prod --open"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"reroute-js": "latest",
|
|
17
|
+
"elysia": "^1.4.12",
|
|
18
|
+
"react": "^19.2.0",
|
|
19
|
+
"react-dom": "^19.2.0",
|
|
20
|
+
"sharp": "^0.34.4"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/bun": "latest",
|
|
24
|
+
"@types/react": "^19.2.2",
|
|
25
|
+
"@types/react-dom": "^19.2.2",
|
|
26
|
+
"typescript": "^5"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from 'reroute-js/core';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
options: {
|
|
5
|
+
app: './src/client/App',
|
|
6
|
+
maxAge: 3600,
|
|
7
|
+
compression: true,
|
|
8
|
+
},
|
|
9
|
+
sitemap: {
|
|
10
|
+
enabled: true,
|
|
11
|
+
baseUrl: 'http://localhost:3003',
|
|
12
|
+
// Test pagination with small page size (default 50k)
|
|
13
|
+
// maxUrlsPerPage: 5,
|
|
14
|
+
// Custom URL extractor - products use SKU instead of default fields
|
|
15
|
+
extractUrl: (item, routePattern) => {
|
|
16
|
+
if (routePattern === '/products' && item.sku) {
|
|
17
|
+
return item.sku;
|
|
18
|
+
}
|
|
19
|
+
return null; // fall back to defaults (slug, id, name, etc.)
|
|
20
|
+
},
|
|
21
|
+
extractLastmod: (item) => item.updatedAt || item.date,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"baseUrl": ".",
|
|
11
|
+
"rootDir": ".",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"outDir": "dist/types",
|
|
15
|
+
"emitDeclarationOnly": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"jsx": "react-jsx",
|
|
21
|
+
"types": ["bun", "node"]
|
|
22
|
+
},
|
|
23
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
24
|
+
"exclude": ["node_modules", "dist"]
|
|
25
|
+
}
|
package/_/store/package.json
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
"description": "A Reroute store application with Tailwind CSS v4",
|
|
5
5
|
"private": true,
|
|
6
6
|
"scripts": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
"types": "tsc --noEmit",
|
|
8
|
+
"gen": "reroute gen",
|
|
9
|
+
"start": "PORT=3005 reroute start --prod",
|
|
10
|
+
"dev": "PORT=3005 reroute dev",
|
|
11
|
+
"build": "reroute build",
|
|
12
|
+
"kill": "bunx kill-port 3005",
|
|
13
|
+
"analyze": "reroute analyze --prod --open"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
16
|
"reroute-js": "latest",
|
package/_/store/src/index.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import { reroute } from 'reroute-js/elysia';
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
const app = new Elysia()
|
|
6
|
-
.use(
|
|
7
|
-
|
|
8
|
-
)
|
|
9
|
-
.listen(Number(Bun.env.PORT || '3001'));
|
|
5
|
+
.use(reroute())
|
|
6
|
+
.listen(Number(Bun.env.PORT || '3005'));
|
|
10
7
|
|
|
11
8
|
console.log(
|
|
12
|
-
`[
|
|
9
|
+
`[app] Store example running at ${app.server?.hostname}:${app.server?.port} on ${Bun.env.NODE_ENV || 'development'}`,
|
|
13
10
|
);
|
package/_/streaming/package.json
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
"description": "A Reroute application",
|
|
5
5
|
"private": true,
|
|
6
6
|
"scripts": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
"types": "tsc --noEmit",
|
|
8
|
+
"gen": "reroute gen",
|
|
9
|
+
"start": "PORT=3006 reroute start --prod",
|
|
10
|
+
"dev": "PORT=3006 reroute dev",
|
|
11
|
+
"build": "reroute build",
|
|
12
|
+
"kill": "bunx kill-port 3006",
|
|
13
|
+
"analyze": "reroute analyze --prod --open"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
16
|
"reroute-js": "latest",
|
package/cli/bin.d.ts
CHANGED