reroute-js 0.45.12-beta.1 → 1.0.0-alpha.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 +102 -6
- package/_/blog/src/client/routes/blog/[layout].tsx +45 -13
- package/_/blog/src/client/routes/blog/content/pt-BR/1-hello-world.tsx +27 -0
- package/_/blog/src/client/routes/index.tsx +4 -4
- package/_/search/src/client/routes/docs/content/content-collections.md +18 -0
- package/_/store/package.json +1 -3
- package/_/store/src/client/components/Header.tsx +8 -20
- package/_/store/src/client/components/ProductCard.tsx +9 -16
- package/_/store/src/client/routes/[404].tsx +20 -31
- package/_/store/src/client/routes/categories/[category].tsx +23 -28
- package/_/store/src/client/routes/categories/index.tsx +19 -27
- package/_/store/src/client/routes/index.tsx +27 -37
- package/_/store/src/client/routes/products/[id].tsx +35 -48
- package/_/store/src/client/routes/products/index.tsx +20 -50
- package/_/store/src/client/theme.css +907 -95
- package/cli/bin.d.ts +2 -2
- package/cli/bin.js +1035 -490
- package/cli/bin.js.map +33 -25
- package/cli/index.d.ts +2 -2
- package/cli/index.js +18 -113
- package/cli/index.js.map +5 -5
- package/cli/src/cli.d.ts +2 -2
- package/cli/src/commands/analyze.d.ts +2 -2
- package/cli/src/commands/build.d.ts +2 -2
- package/cli/src/commands/dev.d.ts +2 -2
- package/cli/src/commands/gen.d.ts +2 -2
- package/cli/src/commands/gen.d.ts.map +1 -1
- package/cli/src/commands/index.d.ts +2 -2
- package/cli/src/commands/init.d.ts +2 -2
- package/cli/src/commands/lib/assets.d.ts +2 -2
- package/cli/src/commands/lib/bundler.d.ts +2 -2
- package/cli/src/commands/lib/command.d.ts +2 -2
- package/cli/src/commands/lib/env.d.ts +2 -2
- package/cli/src/commands/lib/index.d.ts +3 -3
- package/cli/src/commands/lib/index.d.ts.map +1 -1
- package/cli/src/commands/lib/log.d.ts +2 -2
- package/cli/src/commands/lib/markdown/availability.d.ts +2 -2
- package/cli/src/commands/lib/markdown/index.d.ts +2 -2
- package/cli/src/commands/lib/markdown/processor.d.ts +2 -2
- package/cli/src/commands/lib/production.d.ts +2 -2
- package/cli/src/commands/lib/server.d.ts +2 -2
- package/cli/src/commands/lib/streaming/analyzer.d.ts +2 -2
- package/cli/src/commands/lib/streaming/suspense.d.ts +2 -2
- package/cli/src/commands/lib/terminal-ui.d.ts +2 -2
- package/cli/src/commands/lib/theme.d.ts +20 -0
- package/cli/src/commands/lib/theme.d.ts.map +1 -0
- package/cli/src/commands/lib/version.d.ts +2 -2
- package/cli/src/commands/og.d.ts +2 -2
- package/cli/src/commands/start.d.ts +2 -2
- package/cli/src/index.d.ts +2 -2
- package/core/index.d.ts +2 -2
- package/core/index.js +677 -127
- package/core/index.js.map +29 -21
- package/core/src/bundler/hash.d.ts +2 -2
- package/core/src/bundler/index.d.ts +2 -2
- package/core/src/config.d.ts +6 -3
- package/core/src/config.d.ts.map +1 -1
- package/core/src/content/discovery.d.ts +2 -2
- package/core/src/content/index.d.ts +2 -2
- package/core/src/content/metadata.d.ts +2 -2
- package/core/src/i18n/catalog.d.ts +17 -0
- package/core/src/i18n/catalog.d.ts.map +1 -0
- package/core/src/i18n/discover.d.ts +19 -0
- package/core/src/i18n/discover.d.ts.map +1 -0
- package/core/src/i18n/index.d.ts +17 -0
- package/core/src/i18n/index.d.ts.map +1 -0
- package/core/src/i18n/messages.d.ts +18 -0
- package/core/src/i18n/messages.d.ts.map +1 -0
- package/core/src/i18n/negotiate.d.ts +18 -0
- package/core/src/i18n/negotiate.d.ts.map +1 -0
- package/core/src/i18n/pathname.d.ts +34 -0
- package/core/src/i18n/pathname.d.ts.map +1 -0
- package/core/src/i18n/resolve.d.ts +22 -0
- package/core/src/i18n/resolve.d.ts.map +1 -0
- package/core/src/i18n/types.d.ts +68 -0
- package/core/src/i18n/types.d.ts.map +1 -0
- package/core/src/index.d.ts +3 -2
- package/core/src/index.d.ts.map +1 -1
- package/core/src/llms/extractor.d.ts +2 -2
- package/core/src/llms/formatter.d.ts +2 -2
- package/core/src/llms/full-generator.d.ts +2 -2
- package/core/src/llms/index-generator.d.ts +2 -2
- package/core/src/llms/index.d.ts +2 -2
- package/core/src/og/discovery.d.ts +2 -2
- package/core/src/og/index.d.ts +2 -2
- package/core/src/og/meta.d.ts +2 -2
- package/core/src/og/render.d.ts +2 -2
- package/core/src/og/types.d.ts +2 -2
- package/core/src/robots/discovery.d.ts +2 -2
- package/core/src/robots/generator.d.ts +2 -2
- package/core/src/robots/index.d.ts +2 -2
- package/core/src/robots/policies.d.ts +2 -2
- package/core/src/rss/discovery.d.ts +4 -2
- package/core/src/rss/discovery.d.ts.map +1 -1
- package/core/src/rss/generator.d.ts +2 -2
- package/core/src/rss/index.d.ts +2 -2
- package/core/src/sitemap/discovery.d.ts +7 -2
- package/core/src/sitemap/discovery.d.ts.map +1 -1
- package/core/src/sitemap/generator.d.ts +2 -2
- package/core/src/sitemap/generator.d.ts.map +1 -1
- package/core/src/sitemap/index.d.ts +2 -2
- package/core/src/ssr/index.d.ts +2 -2
- package/core/src/ssr/lib/cache.d.ts +2 -2
- package/core/src/ssr/lib/cleanup.d.ts +2 -2
- package/core/src/ssr/lib/collections.d.ts +2 -2
- package/core/src/ssr/lib/compression.d.ts +2 -2
- package/core/src/ssr/lib/compute/content.d.ts +2 -9
- package/core/src/ssr/lib/compute/content.d.ts.map +1 -1
- package/core/src/ssr/lib/compute/index.d.ts +2 -2
- package/core/src/ssr/lib/compute/layouts.d.ts +2 -2
- package/core/src/ssr/lib/compute/routes.d.ts +2 -2
- package/core/src/ssr/lib/compute/routes.d.ts.map +1 -1
- package/core/src/ssr/lib/content-path.d.ts +22 -0
- package/core/src/ssr/lib/content-path.d.ts.map +1 -0
- package/core/src/ssr/lib/data.d.ts +2 -2
- package/core/src/ssr/lib/data.d.ts.map +1 -1
- package/core/src/ssr/lib/html.d.ts +2 -2
- package/core/src/ssr/lib/imports.d.ts +2 -2
- package/core/src/ssr/lib/index.d.ts +2 -2
- package/core/src/ssr/lib/layouts.d.ts +2 -2
- package/core/src/ssr/lib/metadata.d.ts +2 -2
- package/core/src/ssr/lib/metadata.d.ts.map +1 -1
- package/core/src/ssr/lib/mime.d.ts +2 -2
- package/core/src/ssr/lib/modules.d.ts +4 -4
- package/core/src/ssr/lib/modules.d.ts.map +1 -1
- package/core/src/ssr/lib/path.d.ts +2 -2
- package/core/src/ssr/lib/preload.d.ts +2 -2
- package/core/src/ssr/lib/preload.d.ts.map +1 -1
- package/core/src/ssr/lib/route-match.d.ts +2 -2
- package/core/src/ssr/lib/scripts/collections.d.ts +2 -2
- package/core/src/ssr/lib/scripts/data.d.ts +3 -3
- package/core/src/ssr/lib/scripts/data.d.ts.map +1 -1
- package/core/src/ssr/lib/scripts/escape.d.ts +2 -2
- package/core/src/ssr/lib/scripts/feeds.d.ts +2 -2
- package/core/src/ssr/lib/scripts/index.d.ts +2 -2
- package/core/src/ssr/lib/seed.d.ts +2 -2
- package/core/src/ssr/lib/seed.d.ts.map +1 -1
- package/core/src/ssr/lib/serialize.d.ts +2 -2
- package/core/src/ssr/lib/setup.d.ts +4 -4
- package/core/src/ssr/lib/setup.d.ts.map +1 -1
- package/core/src/ssr/lib/styles.d.ts +4 -4
- package/core/src/ssr/lib/styles.d.ts.map +1 -1
- package/core/src/ssr/lib/template.d.ts +2 -2
- package/core/src/ssr/lib/types.d.ts +2 -2
- package/core/src/ssr/render.d.ts +5 -3
- package/core/src/ssr/render.d.ts.map +1 -1
- package/core/src/ssr/stream.d.ts +5 -3
- package/core/src/ssr/stream.d.ts.map +1 -1
- package/elysia/index.d.ts +2 -2
- package/elysia/index.js +891 -196
- package/elysia/index.js.map +35 -26
- package/elysia/src/index.d.ts +2 -2
- package/elysia/src/libs/assets.d.ts +2 -2
- package/elysia/src/libs/cache.d.ts +2 -2
- package/elysia/src/libs/caching.d.ts +2 -2
- package/elysia/src/libs/http.d.ts +2 -2
- package/elysia/src/libs/i18n.d.ts +26 -0
- package/elysia/src/libs/i18n.d.ts.map +1 -0
- package/elysia/src/libs/image.d.ts +2 -2
- package/elysia/src/libs/index.d.ts +2 -2
- package/elysia/src/libs/llms.d.ts +2 -2
- package/elysia/src/libs/response.d.ts +2 -2
- package/elysia/src/libs/serving.d.ts +2 -2
- package/elysia/src/plugin.d.ts +2 -2
- package/elysia/src/routes/artifacts.d.ts +2 -2
- package/elysia/src/routes/content.d.ts +2 -2
- package/elysia/src/routes/image.d.ts +2 -2
- package/elysia/src/routes/index.d.ts +2 -2
- package/elysia/src/routes/internal.d.ts +2 -2
- package/elysia/src/routes/llms.d.ts +2 -2
- package/elysia/src/routes/og.d.ts +2 -2
- package/elysia/src/routes/redirects.d.ts +2 -2
- package/elysia/src/routes/robots.d.ts +2 -2
- package/elysia/src/routes/rss.d.ts +2 -2
- package/elysia/src/routes/rss.d.ts.map +1 -1
- package/elysia/src/routes/search.d.ts +2 -2
- package/elysia/src/routes/search.d.ts.map +1 -1
- package/elysia/src/routes/sitemap.d.ts +2 -2
- package/elysia/src/routes/ssr.d.ts +3 -3
- package/elysia/src/routes/ssr.d.ts.map +1 -1
- package/elysia/src/routes/static.d.ts +2 -2
- package/elysia/src/types.d.ts +2 -2
- package/package.json +8 -10
- package/react/index.d.ts +2 -2
- package/react/index.js +560 -170
- package/react/index.js.map +12 -10
- package/react/src/components/ClientOnly.d.ts +2 -2
- package/react/src/components/ContentRoute.d.ts +3 -3
- package/react/src/components/ContentRoute.d.ts.map +1 -1
- package/react/src/components/Image.d.ts +2 -2
- package/react/src/components/LazyRoute.d.ts +2 -2
- package/react/src/components/Link.d.ts +2 -2
- package/react/src/components/Link.d.ts.map +1 -1
- package/react/src/components/Markdown.d.ts +2 -2
- package/react/src/components/Outlet.d.ts +2 -2
- package/react/src/components/index.d.ts +2 -2
- package/react/src/hooks/index.d.ts +2 -2
- package/react/src/hooks/useContent.d.ts +2 -2
- package/react/src/hooks/useContent.d.ts.map +1 -1
- package/react/src/hooks/useData.d.ts +2 -2
- package/react/src/hooks/useFeed.d.ts +2 -2
- package/react/src/hooks/useLayoutData.d.ts +2 -2
- package/react/src/hooks/useLlms.d.ts +2 -2
- package/react/src/hooks/useNavigate.d.ts +2 -2
- package/react/src/hooks/useParams.d.ts +2 -2
- package/react/src/hooks/useRouter.d.ts +2 -2
- package/react/src/hooks/useSearch.d.ts +2 -2
- package/react/src/hooks/useSearch.d.ts.map +1 -1
- package/react/src/hooks/useSearchParams.d.ts +2 -2
- package/react/src/hooks/useToc.d.ts +2 -2
- package/react/src/i18n/LocaleProvider.d.ts +49 -0
- package/react/src/i18n/LocaleProvider.d.ts.map +1 -0
- package/react/src/i18n/helpers.d.ts +24 -0
- package/react/src/i18n/helpers.d.ts.map +1 -0
- package/react/src/i18n/index.d.ts +13 -0
- package/react/src/i18n/index.d.ts.map +1 -0
- package/react/src/index.d.ts +3 -2
- package/react/src/index.d.ts.map +1 -1
- package/react/src/lib/collection.d.ts +2 -2
- package/react/src/lib/content.d.ts +4 -3
- package/react/src/lib/content.d.ts.map +1 -1
- package/react/src/lib/head.d.ts +2 -2
- package/react/src/lib/index.d.ts +2 -2
- package/react/src/lib/lazy-route.d.ts +2 -2
- package/react/src/lib/route-loader.d.ts +2 -2
- package/react/src/providers/ContentProvider.d.ts +3 -3
- package/react/src/providers/ContentProvider.d.ts.map +1 -1
- package/react/src/providers/RerouteProvider.d.ts +8 -6
- package/react/src/providers/RerouteProvider.d.ts.map +1 -1
- package/react/src/providers/RouterProvider.d.ts +2 -2
- package/react/src/providers/index.d.ts +2 -2
- package/react/src/types/any.d.ts +2 -2
- package/react/src/types/index.d.ts +2 -2
- package/react/src/types/router.d.ts +2 -2
- package/telemetry/react.d.ts +2 -2
- package/telemetry/react.js +3 -3
- package/telemetry/react.js.map +1 -1
- package/telemetry/server.d.ts +2 -2
- package/telemetry/server.js +56 -4
- package/telemetry/server.js.map +20 -15
- package/telemetry/src/react/api.d.ts +2 -2
- package/telemetry/src/react/context.d.ts +2 -2
- package/telemetry/src/react/index.d.ts +2 -2
- package/telemetry/src/react/telemetry.d.ts +2 -2
- package/telemetry/src/server/context.d.ts +2 -2
- package/telemetry/src/server/headers/extractor.d.ts +2 -2
- package/telemetry/src/server/headers/index.d.ts +2 -2
- package/telemetry/src/server/headers/presets.d.ts +2 -2
- package/telemetry/src/server/index.d.ts +2 -2
- package/telemetry/src/server/instrumentation.d.ts +2 -2
- package/telemetry/src/server/plugin.d.ts +2 -2
- package/telemetry/src/server/sourcemap.d.ts +2 -2
- package/cli/src/commands/lib/tailwind.d.ts +0 -20
- package/cli/src/commands/lib/tailwind.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -22,6 +22,12 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
|
|
|
22
22
|
- [Environment Files](#environment-files)
|
|
23
23
|
- [Security](#security)
|
|
24
24
|
- [TypeScript Support](#typescript-support)
|
|
25
|
+
- [🌍 Internationalization (i18n)](#-internationalization-i18n)
|
|
26
|
+
- [Enable](#enable)
|
|
27
|
+
- [URL Rules](#url-rules)
|
|
28
|
+
- [UI Strings](#ui-strings)
|
|
29
|
+
- [SEO & Feeds](#seo--feeds)
|
|
30
|
+
- [Config](#config)
|
|
25
31
|
- [📝 Markdown Routes](#-markdown-routes)
|
|
26
32
|
- [Installation](#installation)
|
|
27
33
|
- [Creating Markdown Routes](#creating-markdown-routes)
|
|
@@ -74,6 +80,7 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
|
|
|
74
80
|
|
|
75
81
|
### 📄 Content Management
|
|
76
82
|
- 📚 **Content Collections** - Organize content by collections (blog posts, docs, etc.)
|
|
83
|
+
- 🌍 **Internationalization** - Opt-in, collection-scoped i18n via `locales/` + `content/{locale}/`
|
|
77
84
|
- 🔍 **Content Discovery** - Automatic scanning and indexing of content files
|
|
78
85
|
- 🏷️ **Metadata Extraction** - Support for content metadata
|
|
79
86
|
- 📑 **Content Registry** - Pre-generated content index for fast lookups
|
|
@@ -116,8 +123,11 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
|
|
|
116
123
|
- `useLlms()` - Discover LLM-friendly content URLs for current route
|
|
117
124
|
- `useSearch()` - Client-side content search with debouncing
|
|
118
125
|
- `useToc()` - Auto-extract table of contents from markdown headings
|
|
126
|
+
- 🌍 **i18n**:
|
|
127
|
+
- `t(key, values?)` - Plain translator (ICU MessageFormat); not a hook
|
|
128
|
+
- `useLocale()` - Active locale, available locales, and `setLocale()`
|
|
119
129
|
- 🧩 **Components**:
|
|
120
|
-
- `<Link>` - Client-side navigation with automatic prefetching
|
|
130
|
+
- `<Link>` - Client-side navigation with automatic prefetching (localizes content hrefs)
|
|
121
131
|
- `<Image>` - Optimized images with automatic format negotiation and lazy loading
|
|
122
132
|
- `<Outlet>` - Render nested route components
|
|
123
133
|
- `<ContentRoute>` - Dynamic content rendering with metadata injection
|
|
@@ -169,11 +179,9 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
|
|
|
169
179
|
- ⚡ **React 19 Streaming** - Native `renderToReadableStream` for optimal performance
|
|
170
180
|
|
|
171
181
|
### 🎨 Styling & Theming
|
|
172
|
-
- 🎨 **
|
|
182
|
+
- 🎨 **Native CSS** - Author styles in `src/client/theme.css` with no CSS framework required
|
|
173
183
|
- ⚡ **Live CSS Reload** - Instant style updates in development mode
|
|
174
|
-
-
|
|
175
|
-
- 🚀 **Oxide Engine** - 5x faster builds with the new Tailwind compiler
|
|
176
|
-
- 🔧 **Zero Config** - Works out of the box when `@tailwindcss/cli` is installed
|
|
184
|
+
- 🔧 **Zero Config** - Theme CSS is synced automatically during `reroute gen`
|
|
177
185
|
|
|
178
186
|
### 🎨 Developer Experience
|
|
179
187
|
- 📁 **Project Templates** - Quick start with `basic`, `blog`, or `store` templates
|
|
@@ -208,6 +216,7 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
|
|
|
208
216
|
### 🗺️ Sitemap Generation
|
|
209
217
|
- 🔍 **Auto-Discovery** - Automatically discovers static routes, content collections, and ssr.data routes
|
|
210
218
|
- 📄 **XML Generation** - Standards-compliant sitemap.xml following Google's specifications
|
|
219
|
+
- 🌍 **i18n Alternates** - `xhtml:link` hreflang entries for localized content
|
|
211
220
|
- 💾 **Smart Caching** - Generated on first request and cached according to maxAge
|
|
212
221
|
- 📊 **Auto-Pagination** - Automatically splits into multiple files for sites with 50k+ URLs
|
|
213
222
|
- 🎨 **Custom Extractors** - Content-agnostic URL and date extraction via configuration
|
|
@@ -225,6 +234,7 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
|
|
|
225
234
|
- 🔍 **Auto-Discovery** - Discovers content collections, route ssr.data, and layout ssr.data
|
|
226
235
|
- 📄 **RSS & Atom Support** - Standards-compliant RSS 2.0 and Atom 1.0 formats
|
|
227
236
|
- 📚 **Per-Collection Feeds** - Each collection gets its own feed (e.g., /blog/feed.xml)
|
|
237
|
+
- 🌍 **Locale Filter** - `?lang=` filters feed items by content locale
|
|
228
238
|
- 💾 **Smart Caching** - Generated on first request and cached according to maxAge
|
|
229
239
|
- 🎨 **Custom Extractors** - Content-agnostic extraction for title, description, author, pubDate
|
|
230
240
|
|
|
@@ -623,6 +633,92 @@ interface ImportMeta {
|
|
|
623
633
|
}
|
|
624
634
|
```
|
|
625
635
|
|
|
636
|
+
## 🌍 Internationalization (i18n)
|
|
637
|
+
|
|
638
|
+
Opt-in and **collection-scoped** — not site-wide locale prefixes. Enabled only when a `locales/` directory exists at the app root.
|
|
639
|
+
|
|
640
|
+
### Enable
|
|
641
|
+
|
|
642
|
+
```
|
|
643
|
+
locales/
|
|
644
|
+
en-US.json # default locale catalog
|
|
645
|
+
pt-BR.json
|
|
646
|
+
src/client/routes/blog/content/
|
|
647
|
+
hello-world.tsx # default locale (unprefixed URL)
|
|
648
|
+
pt-BR/
|
|
649
|
+
hello-world.tsx # /blog/pt-BR/hello-world
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
```json
|
|
653
|
+
// locales/en-US.json
|
|
654
|
+
{
|
|
655
|
+
"nav": { "home": "Home" },
|
|
656
|
+
"blog": {
|
|
657
|
+
"postsCount": "{count, plural, =0 {No posts} one {# post} other {# posts}}"
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### URL Rules
|
|
663
|
+
|
|
664
|
+
| Locale | URL |
|
|
665
|
+
|--------|-----|
|
|
666
|
+
| Default (`en-US`) | `/blog/hello-world` |
|
|
667
|
+
| Other | `/blog/pt-BR/hello-world` |
|
|
668
|
+
| Non-content routes | unchanged (`/about`, `/`) |
|
|
669
|
+
|
|
670
|
+
- Flat files under `content/` are the default locale; nest under `content/{locale}/` for translations
|
|
671
|
+
- Collections without locale folders behave as before
|
|
672
|
+
- Default-locale prefix (`/blog/en-US/...`) 302s to the unprefixed URL
|
|
673
|
+
- Optional negotiate: `Accept-Language` / cookie can 302 default content URLs to a localized path (`i18n.redirectContentOnNegotiate`, default `true`)
|
|
674
|
+
|
|
675
|
+
### UI Strings
|
|
676
|
+
|
|
677
|
+
```tsx
|
|
678
|
+
import { t, useLocale, Link } from 'reroute-js/react';
|
|
679
|
+
|
|
680
|
+
function Nav() {
|
|
681
|
+
const { locale, locales, setLocale } = useLocale();
|
|
682
|
+
return (
|
|
683
|
+
<>
|
|
684
|
+
<Link to="/">{t('nav.home')}</Link>
|
|
685
|
+
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
|
|
686
|
+
{locales.map((l) => (
|
|
687
|
+
<option key={l} value={l}>{l}</option>
|
|
688
|
+
))}
|
|
689
|
+
</select>
|
|
690
|
+
<footer>{t('blog.postsCount', { count: 5 })}</footer>
|
|
691
|
+
</>
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
- `t(key, values?)` — plain function (not a hook); ICU MessageFormat supported
|
|
697
|
+
- `useLocale()` — `{ locale, locales, defaultLocale, enabled, setLocale }`
|
|
698
|
+
- `Link` auto-localizes content hrefs; cookie (`reroute-locale`) + `Accept-Language` resolve UI locale
|
|
699
|
+
|
|
700
|
+
### SEO & Feeds
|
|
701
|
+
|
|
702
|
+
- `hreflang` + `og:locale` on content pages
|
|
703
|
+
- Sitemap includes `xhtml:link` alternates
|
|
704
|
+
- RSS / search accept `?lang=` to filter by locale
|
|
705
|
+
|
|
706
|
+
### Config
|
|
707
|
+
|
|
708
|
+
```tsx
|
|
709
|
+
// reroute.config.ts
|
|
710
|
+
export default defineConfig({
|
|
711
|
+
i18n: {
|
|
712
|
+
defaultLocale: 'en-US', // unprefixed content URLs
|
|
713
|
+
localesDir: 'locales', // catalog directory
|
|
714
|
+
cookie: 'reroute-locale',
|
|
715
|
+
redirectContentOnNegotiate: true, // 302 default content → localized URL
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Feature still requires a `locales/` directory — config only overrides defaults.
|
|
721
|
+
|
|
626
722
|
## 📝 Markdown Routes
|
|
627
723
|
|
|
628
724
|
Reroute supports native markdown and MDX files as routes with frontmatter support and syntax highlighting.
|
|
@@ -1693,7 +1789,7 @@ Built-in tracing for debugging SSR, 404s, and cache behavior. Automatically trac
|
|
|
1693
1789
|
|
|
1694
1790
|
- [Examples](./examples) - Example projects to get started
|
|
1695
1791
|
- [Packages](./packages) - Reroute entrypoints
|
|
1696
|
-
- [GitHub](https://github.com/
|
|
1792
|
+
- [GitHub](https://github.com/Intenseloop/reroute) - Source code and issues
|
|
1697
1793
|
|
|
1698
1794
|
## 🏗️ Build & Deployment
|
|
1699
1795
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Link, Outlet, useData } from 'reroute-js/react';
|
|
2
1
|
import type { SSR } from 'reroute-js/core';
|
|
2
|
+
import { Link, Outlet, t, useData, useLocale } from 'reroute-js/react';
|
|
3
3
|
|
|
4
4
|
// Layout data type
|
|
5
5
|
type LayoutData = {
|
|
@@ -16,13 +16,17 @@ export const ssr: SSR<LayoutData> = {
|
|
|
16
16
|
// Get last updated timestamp from package.json mtime
|
|
17
17
|
let lastUpdated: string | undefined;
|
|
18
18
|
try {
|
|
19
|
-
const pkgPath = await import('node:path').then(p =>
|
|
20
|
-
|
|
19
|
+
const pkgPath = await import('node:path').then((p) =>
|
|
20
|
+
p.join(process.cwd(), 'package.json'),
|
|
21
|
+
);
|
|
22
|
+
const stats = await import('node:fs/promises').then((fs) =>
|
|
23
|
+
fs.stat(pkgPath),
|
|
24
|
+
);
|
|
21
25
|
lastUpdated = stats.mtime.toISOString();
|
|
22
26
|
} catch {
|
|
23
27
|
lastUpdated = new Date().toISOString();
|
|
24
28
|
}
|
|
25
|
-
|
|
29
|
+
|
|
26
30
|
return {
|
|
27
31
|
siteTitle: 'Reroute Blog',
|
|
28
32
|
tagline: 'Fast, Modern, File-Based Routing',
|
|
@@ -39,11 +43,11 @@ export const meta = {
|
|
|
39
43
|
};
|
|
40
44
|
|
|
41
45
|
export default function BlogLayout() {
|
|
42
|
-
// Access layout data - it just works!
|
|
43
46
|
const data = useData<LayoutData>();
|
|
47
|
+
const { locale, locales, setLocale, enabled } = useLocale();
|
|
48
|
+
|
|
44
49
|
return (
|
|
45
50
|
<div style={{ minHeight: '100vh', background: '#f9f9f9' }}>
|
|
46
|
-
{/* Blog Header */}
|
|
47
51
|
<header
|
|
48
52
|
style={{
|
|
49
53
|
background: '#fff',
|
|
@@ -57,6 +61,8 @@ export default function BlogLayout() {
|
|
|
57
61
|
display: 'flex',
|
|
58
62
|
alignItems: 'center',
|
|
59
63
|
justifyContent: 'space-between',
|
|
64
|
+
gap: '1rem',
|
|
65
|
+
flexWrap: 'wrap',
|
|
60
66
|
}}
|
|
61
67
|
>
|
|
62
68
|
<Link
|
|
@@ -68,7 +74,7 @@ export default function BlogLayout() {
|
|
|
68
74
|
color: '#333',
|
|
69
75
|
}}
|
|
70
76
|
>
|
|
71
|
-
📝 {
|
|
77
|
+
📝 {t('blog.title')}
|
|
72
78
|
</Link>
|
|
73
79
|
<div
|
|
74
80
|
style={{ display: 'flex', gap: '1.5rem', alignItems: 'center' }}
|
|
@@ -81,7 +87,7 @@ export default function BlogLayout() {
|
|
|
81
87
|
fontSize: '0.875rem',
|
|
82
88
|
}}
|
|
83
89
|
>
|
|
84
|
-
|
|
90
|
+
{t('nav.home')}
|
|
85
91
|
</Link>
|
|
86
92
|
<Link
|
|
87
93
|
to='/about'
|
|
@@ -91,21 +97,43 @@ export default function BlogLayout() {
|
|
|
91
97
|
fontSize: '0.875rem',
|
|
92
98
|
}}
|
|
93
99
|
>
|
|
94
|
-
|
|
100
|
+
{t('nav.about')}
|
|
95
101
|
</Link>
|
|
102
|
+
{enabled && locales.length > 1 && (
|
|
103
|
+
<label
|
|
104
|
+
style={{
|
|
105
|
+
display: 'flex',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
gap: '0.35rem',
|
|
108
|
+
fontSize: '0.875rem',
|
|
109
|
+
color: '#666',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
{t('blog.language')}
|
|
113
|
+
<select
|
|
114
|
+
value={locale}
|
|
115
|
+
onChange={(e) => setLocale(e.target.value)}
|
|
116
|
+
style={{ fontSize: '0.875rem' }}
|
|
117
|
+
>
|
|
118
|
+
{locales.map((loc) => (
|
|
119
|
+
<option key={loc} value={loc}>
|
|
120
|
+
{loc}
|
|
121
|
+
</option>
|
|
122
|
+
))}
|
|
123
|
+
</select>
|
|
124
|
+
</label>
|
|
125
|
+
)}
|
|
96
126
|
</div>
|
|
97
127
|
</nav>
|
|
98
128
|
</div>
|
|
99
129
|
</header>
|
|
100
130
|
|
|
101
|
-
{/* Blog Content */}
|
|
102
131
|
<main>
|
|
103
132
|
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
|
|
104
133
|
<Outlet />
|
|
105
134
|
</div>
|
|
106
135
|
</main>
|
|
107
136
|
|
|
108
|
-
{/* Blog Footer */}
|
|
109
137
|
<footer
|
|
110
138
|
style={{
|
|
111
139
|
marginTop: '4rem',
|
|
@@ -117,10 +145,14 @@ export default function BlogLayout() {
|
|
|
117
145
|
background: '#fff',
|
|
118
146
|
}}
|
|
119
147
|
>
|
|
120
|
-
<p
|
|
148
|
+
<p>
|
|
149
|
+
© {new Date().getFullYear()} {t('blog.title')}. Built with Bun,
|
|
150
|
+
Elysia, and React.
|
|
151
|
+
</p>
|
|
121
152
|
{data?.totalPosts && (
|
|
122
153
|
<p style={{ marginTop: '0.5rem', fontSize: '0.75rem' }}>
|
|
123
|
-
{data.totalPosts}
|
|
154
|
+
{t('blog.postsCount', { count: data.totalPosts })} • Last updated:{' '}
|
|
155
|
+
{new Date(data.lastUpdated).toLocaleString(locale)}
|
|
124
156
|
</p>
|
|
125
157
|
)}
|
|
126
158
|
</footer>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Page metadata and optional SSR extras. Reroute SSR will use these
|
|
2
|
+
// to generate <title>, <meta name="description"> and append any custom head.
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'Olá Mundo',
|
|
5
|
+
description: 'Este é o primeiro post usando Reroute 🎉',
|
|
6
|
+
excerpt: 'Este é um ótimo post',
|
|
7
|
+
date: '2025-10-20',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const ssr = {
|
|
11
|
+
head: [
|
|
12
|
+
'<meta property="og:type" content="article" />',
|
|
13
|
+
'<meta name="twitter:card" content="summary_large_image" />',
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function BlogPost() {
|
|
18
|
+
return (
|
|
19
|
+
<div>
|
|
20
|
+
<h1>Olá Mundo</h1>
|
|
21
|
+
<p>Bem-vindo ao meu blog!!!</p>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { meta, ssr };
|
|
27
|
+
export default BlogPost;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Link } from 'reroute-js/react';
|
|
1
|
+
import { Link, t } from 'reroute-js/react';
|
|
2
2
|
|
|
3
3
|
export default function HomePage() {
|
|
4
4
|
return (
|
|
5
5
|
<main style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
|
|
6
6
|
<h1>🏠 Welcome to Reroute!</h1>
|
|
7
|
-
<p>
|
|
7
|
+
<p>{t('home.headline')}</p>
|
|
8
8
|
|
|
9
9
|
<section style={{ marginTop: '2rem' }}>
|
|
10
10
|
<h2>Navigation</h2>
|
|
@@ -25,7 +25,7 @@ export default function HomePage() {
|
|
|
25
25
|
borderRadius: '4px',
|
|
26
26
|
}}
|
|
27
27
|
>
|
|
28
|
-
|
|
28
|
+
{t('nav.about')}
|
|
29
29
|
</Link>
|
|
30
30
|
<Link
|
|
31
31
|
to='/blog'
|
|
@@ -36,7 +36,7 @@ export default function HomePage() {
|
|
|
36
36
|
borderRadius: '4px',
|
|
37
37
|
}}
|
|
38
38
|
>
|
|
39
|
-
|
|
39
|
+
{t('home.cta')}
|
|
40
40
|
</Link>
|
|
41
41
|
<Link
|
|
42
42
|
to='/docs'
|
|
@@ -35,6 +35,24 @@ src/client/routes/
|
|
|
35
35
|
└── post-3.tsx
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
### Multilingual content (opt-in)
|
|
39
|
+
|
|
40
|
+
Add a `locales/` folder at the app root with JSON catalogs (e.g. `en-US.json`, `pt-BR.json`) to enable i18n. Then nest translations under `content/{locale}/`:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
locales/
|
|
44
|
+
en-US.json
|
|
45
|
+
pt-BR.json
|
|
46
|
+
src/client/routes/blog/content/
|
|
47
|
+
en-US/post-1.md # or keep flat files as the default locale
|
|
48
|
+
pt-BR/post-1.md
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Default locale URLs stay unprefixed: `/blog/post-1`
|
|
52
|
+
- Other locales: `/blog/pt-BR/post-1`
|
|
53
|
+
- Collections without locale folders are unchanged
|
|
54
|
+
- Use `t('nav.home')` for UI strings (ICU MessageFormat supported)
|
|
55
|
+
|
|
38
56
|
### Content Files
|
|
39
57
|
|
|
40
58
|
#### Markdown Files
|
package/_/store/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "{{PROJECT_NAME}}",
|
|
3
3
|
"version": "0.0.0",
|
|
4
|
-
"description": "A Reroute store application
|
|
4
|
+
"description": "A Reroute store application",
|
|
5
5
|
"private": true,
|
|
6
6
|
"scripts": {
|
|
7
7
|
"types": "tsc --noEmit",
|
|
@@ -23,8 +23,6 @@
|
|
|
23
23
|
"@types/bun": "latest",
|
|
24
24
|
"@types/react": "^19.2.2",
|
|
25
25
|
"@types/react-dom": "^19.2.2",
|
|
26
|
-
"@tailwindcss/cli": "^4.1.16",
|
|
27
|
-
"tailwindcss": "^4.1.16",
|
|
28
26
|
"typescript": "^5"
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -2,34 +2,22 @@ import { Link } from 'reroute-js/react';
|
|
|
2
2
|
|
|
3
3
|
export default function Header() {
|
|
4
4
|
return (
|
|
5
|
-
<header className='
|
|
5
|
+
<header className='site-header'>
|
|
6
6
|
<div className='container-custom'>
|
|
7
|
-
<div className='
|
|
8
|
-
<Link
|
|
9
|
-
|
|
10
|
-
className='flex items-center gap-2 text-2xl font-bold text-primary-600 hover:text-primary-700 transition-colors'
|
|
11
|
-
>
|
|
12
|
-
<span className='text-3xl'>🛒</span>
|
|
7
|
+
<div className='header-inner'>
|
|
8
|
+
<Link to='/' className='header-logo'>
|
|
9
|
+
<span className='header-logo-icon'>🛒</span>
|
|
13
10
|
<span>Reroute Store</span>
|
|
14
11
|
</Link>
|
|
15
12
|
|
|
16
|
-
<nav className='
|
|
17
|
-
<Link
|
|
18
|
-
to='/'
|
|
19
|
-
className='link-muted font-medium hover:text-primary-600'
|
|
20
|
-
>
|
|
13
|
+
<nav className='header-nav'>
|
|
14
|
+
<Link to='/' className='nav-link'>
|
|
21
15
|
Home
|
|
22
16
|
</Link>
|
|
23
|
-
<Link
|
|
24
|
-
to='/products'
|
|
25
|
-
className='link-muted font-medium hover:text-primary-600'
|
|
26
|
-
>
|
|
17
|
+
<Link to='/products' className='nav-link'>
|
|
27
18
|
Products
|
|
28
19
|
</Link>
|
|
29
|
-
<Link
|
|
30
|
-
to='/categories'
|
|
31
|
-
className='link-muted font-medium hover:text-primary-600'
|
|
32
|
-
>
|
|
20
|
+
<Link to='/categories' className='nav-link'>
|
|
33
21
|
Categories
|
|
34
22
|
</Link>
|
|
35
23
|
</nav>
|
|
@@ -8,12 +8,9 @@ interface ProductCardProps {
|
|
|
8
8
|
|
|
9
9
|
export default function ProductCard({ product }: ProductCardProps) {
|
|
10
10
|
return (
|
|
11
|
-
<div className='card card-hover
|
|
12
|
-
<Link
|
|
13
|
-
|
|
14
|
-
className='block h-full no-underline text-inherit'
|
|
15
|
-
>
|
|
16
|
-
<div className='w-full h-60 bg-gray-50 flex items-center justify-center p-4'>
|
|
11
|
+
<div className='card card-hover product-card'>
|
|
12
|
+
<Link to={`/products/${product.id}`} className='product-card-link'>
|
|
13
|
+
<div className='product-card-image-wrap'>
|
|
17
14
|
<Image
|
|
18
15
|
src={product.image}
|
|
19
16
|
alt={product.title}
|
|
@@ -21,25 +18,21 @@ export default function ProductCard({ product }: ProductCardProps) {
|
|
|
21
18
|
height={180}
|
|
22
19
|
quality={75}
|
|
23
20
|
sizes='(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 180px'
|
|
24
|
-
className='
|
|
21
|
+
className='product-card-image'
|
|
25
22
|
style={{ viewTransitionName: `product-image-${product.id}` }}
|
|
26
23
|
/>
|
|
27
24
|
</div>
|
|
28
25
|
|
|
29
|
-
<div className='
|
|
26
|
+
<div className='product-card-body'>
|
|
30
27
|
<div className='badge-primary'>{product.category}</div>
|
|
31
28
|
|
|
32
|
-
<h3 className='
|
|
33
|
-
{product.title}
|
|
34
|
-
</h3>
|
|
29
|
+
<h3 className='product-card-title'>{product.title}</h3>
|
|
35
30
|
|
|
36
|
-
<div className='
|
|
37
|
-
<span className='
|
|
38
|
-
${product.price.toFixed(2)}
|
|
39
|
-
</span>
|
|
31
|
+
<div className='product-card-footer'>
|
|
32
|
+
<span className='price'>${product.price.toFixed(2)}</span>
|
|
40
33
|
|
|
41
34
|
{product.rating && (
|
|
42
|
-
<div className='
|
|
35
|
+
<div className='product-rating'>
|
|
43
36
|
<span>⭐</span>
|
|
44
37
|
<span>
|
|
45
38
|
{product.rating.rate} ({product.rating.count})
|
|
@@ -6,20 +6,18 @@ export default function NotFoundPage() {
|
|
|
6
6
|
<>
|
|
7
7
|
<Header />
|
|
8
8
|
|
|
9
|
-
<main className='
|
|
10
|
-
<div className='
|
|
11
|
-
<div className='
|
|
12
|
-
<div className='
|
|
13
|
-
<h1 className='
|
|
14
|
-
|
|
15
|
-
</h1>
|
|
16
|
-
<p className='text-xl text-gray-600 mb-8'>
|
|
9
|
+
<main className='page-main page-main-lg'>
|
|
10
|
+
<div className='not-found-content'>
|
|
11
|
+
<div className='animate-slide-up'>
|
|
12
|
+
<div className='not-found-code'>404</div>
|
|
13
|
+
<h1 className='not-found-title'>Page Not Found</h1>
|
|
14
|
+
<p className='not-found-lead'>
|
|
17
15
|
Oops! The page you're looking for doesn't exist. It might have
|
|
18
16
|
been moved or deleted.
|
|
19
17
|
</p>
|
|
20
18
|
</div>
|
|
21
19
|
|
|
22
|
-
<div className='
|
|
20
|
+
<div className='not-found-actions animate-in'>
|
|
23
21
|
<Link to='/' className='btn-primary btn-lg'>
|
|
24
22
|
🏠 Go Home
|
|
25
23
|
</Link>
|
|
@@ -28,32 +26,23 @@ export default function NotFoundPage() {
|
|
|
28
26
|
</Link>
|
|
29
27
|
</div>
|
|
30
28
|
|
|
31
|
-
<div className='
|
|
32
|
-
<Link
|
|
33
|
-
|
|
34
|
-
className='
|
|
35
|
-
|
|
36
|
-
<div className='text-4xl mb-3'>🏷️</div>
|
|
37
|
-
<h3 className='font-semibold mb-2'>Categories</h3>
|
|
38
|
-
<p className='text-sm text-gray-600'>Browse by category</p>
|
|
29
|
+
<div className='not-found-links animate-scale'>
|
|
30
|
+
<Link to='/categories' className='card card-hover card-padded'>
|
|
31
|
+
<div className='not-found-card-icon'>🏷️</div>
|
|
32
|
+
<h3 className='not-found-card-title'>Categories</h3>
|
|
33
|
+
<p className='not-found-card-desc'>Browse by category</p>
|
|
39
34
|
</Link>
|
|
40
35
|
|
|
41
|
-
<Link
|
|
42
|
-
|
|
43
|
-
className='
|
|
44
|
-
|
|
45
|
-
<div className='text-4xl mb-3'>📦</div>
|
|
46
|
-
<h3 className='font-semibold mb-2'>All Products</h3>
|
|
47
|
-
<p className='text-sm text-gray-600'>View our catalog</p>
|
|
36
|
+
<Link to='/products' className='card card-hover card-padded'>
|
|
37
|
+
<div className='not-found-card-icon'>📦</div>
|
|
38
|
+
<h3 className='not-found-card-title'>All Products</h3>
|
|
39
|
+
<p className='not-found-card-desc'>View our catalog</p>
|
|
48
40
|
</Link>
|
|
49
41
|
|
|
50
|
-
<Link
|
|
51
|
-
|
|
52
|
-
className='
|
|
53
|
-
|
|
54
|
-
<div className='text-4xl mb-3'>⚡</div>
|
|
55
|
-
<h3 className='font-semibold mb-2'>Featured</h3>
|
|
56
|
-
<p className='text-sm text-gray-600'>See what's popular</p>
|
|
42
|
+
<Link to='/' className='card card-hover card-padded'>
|
|
43
|
+
<div className='not-found-card-icon'>⚡</div>
|
|
44
|
+
<h3 className='not-found-card-title'>Featured</h3>
|
|
45
|
+
<p className='not-found-card-desc'>See what's popular</p>
|
|
57
46
|
</Link>
|
|
58
47
|
</div>
|
|
59
48
|
</div>
|