routerino 2.1.0 → 2.2.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 CHANGED
@@ -2,16 +2,20 @@
2
2
 
3
3
  > A lightweight, SEO-optimized React router for modern websites and applications
4
4
 
5
+ For teams who want SPA simplicity with search-friendly static HTML, Open Graph previews, and **no framework lock-in.**
6
+
7
+ <!-- [**Live Example**](https://www.papoir.com) | [**Starter Template**](https://github.com/nerds-with-keyboards/routerino-starter) -->
8
+
5
9
  Routerino is a zero-dependency router for React designed for optimal SEO performance in client-side rendered applications. Built for modern web architectures like JAMStack applications and Vite-powered React sites, it provides route & meta tag management, sitemap generation, and static site generation or [prerender](https://github.com/prerender/prerender) support to ensure your React applications are fully discoverable by search engines.
6
10
 
7
11
  ## Why Routerino?
8
12
 
9
- - **SEO-First Design**: Automatic meta tag management, sitemap generation, and prerender support ensure maximum search engine visibility
10
- - **Zero Dependencies**: Keeps bundle size minimal and reduces supply-chain vulnerabilities
11
- - **Simple API**: No special `Link` components required - use standard HTML anchors and navigate programmatically with standard browser APIs
12
- - **Static Site Generation**: Build-tool agnostic static HTML generation for improved performance and SEO
13
- - **Production Ready**: Includes Docker-based prerender server for easy deployments
14
- - **Single File Core**: The entire routing logic fits in one file (~420 lines), making it easy to understand and customize
13
+ - SEO-First Design: Automatic meta tag management, sitemap generation, and prerender support ensure maximum search engine visibility
14
+ - Zero Added Dependencies: Keeps bundle size minimal and reduces supply-chain vulnerabilities
15
+ - Simple API: No special `Link` components required - use standard HTML anchors and navigate programmatically with standard browser APIs
16
+ - Static Site Generation: Build-tool agnostic static HTML generation for improved performance and SEO
17
+ - Prerender Ready: Works with prerender servers (such as prerender/prerender) via dedicated meta tags for correct crawler status codes
18
+ - Single File Core: The entire routing logic fits in one file, making it easy to understand and customize
15
19
 
16
20
  ## Table of Contents
17
21
 
@@ -19,13 +23,12 @@ Routerino is a zero-dependency router for React designed for optimal SEO perform
19
23
  - [Installation](#installation)
20
24
  - [Usage](#usage)
21
25
  - [Props](#props-arguments)
22
- - [Get route parameters](#get-route-parameters-and-the-current-route-and-updating-head-tags)
26
+ - [useRouterino Hook](#using-the-userouterino-hook)
23
27
  - [updateHeadTag](#updateheadtag)
28
+ - [TypeScript Support](#typescript-support)
24
29
  - [Best Practices](#routerino-best-practices)
25
30
  - [Generating a Sitemap](#generating-a-sitemap-from-routes)
26
31
  - [Static Site Generation](#static-site-generation)
27
- - [Deployment Guides](#deployment-guides)
28
- - [Prerender Server (Docker)](#prerender-server-docker)
29
32
  - [How-to Guides & Examples](#how-to-guides--example-code)
30
33
  - [Starting a New Project](#starting-a-new-react-project-with-routerino)
31
34
  - [Full React Example](#full-react-example)
@@ -39,29 +42,29 @@ Routerino is a zero-dependency router for React designed for optimal SEO perform
39
42
  ## Quick Start
40
43
 
41
44
  ```jsx
42
- <Routerino
43
- title="Example.com"
44
- routes={[
45
- {
46
- path: "/",
47
- element: <p>This is the home page!</p>,
48
- title: "My Home Page!",
49
- description: "Welcome to my home page!",
50
- },
51
- {
52
- path: "/blog/my-first-post/",
53
- element: (
54
- <article>
55
- <h1>My First Post</h1>
56
- <p>Lorem ipsum...</p>
57
- </article>
58
- ),
59
- title: "My First Post",
60
- description: "The first post on my new home page!",
61
- tags: [{ property: "og:type", content="article" }]
62
- },
63
- ]}
64
- />
45
+ // export your routes for SSG, not required for CSR-only
46
+ export const routes = [
47
+ {
48
+ path: "/",
49
+ element: <p>This is the home page!</p>,
50
+ title: "My Home Page!",
51
+ description: "Welcome to my home page!",
52
+ },
53
+ {
54
+ path: "/blog/my-first-post/",
55
+ element: (
56
+ <article>
57
+ <h1>My First Post</h1>
58
+ <p>Lorem ipsum...</p>
59
+ </article>
60
+ ),
61
+ title: "My First Post",
62
+ description: "The first post on my new home page!",
63
+ tags: [{ property: "og:type", content: "article" }],
64
+ },
65
+ ];
66
+
67
+ <Routerino title="Example.com" routes={routes} />;
65
68
  ```
66
69
 
67
70
  This simple configuration automatically handles routing, meta tags, and SEO optimization for your React application.
@@ -81,6 +84,7 @@ This simple configuration automatically handles routing, meta tags, and SEO opti
81
84
  - Generate static HTML files for each route with proper meta tags
82
85
  - Implement SEO best practices out-of-the-box
83
86
  - Optimize for Googlebot with pre-rendering support
87
+ - Automatic image optimization with blur placeholders
84
88
 
85
89
  - Enhanced User Experience
86
90
  - Support for sharing and social preview metadata
@@ -88,46 +92,115 @@ This simple configuration automatically handles routing, meta tags, and SEO opti
88
92
 
89
93
  ## Installation
90
94
 
91
- Ensure that you have React and React DOM installed in your project as peer dependencies. To add as a dev dependency:
92
-
93
95
  ```sh
94
- npm i routerino -D
96
+ npm i routerino
95
97
  ```
96
98
 
99
+ Note: Routerino requires React, React DOM, and PropTypes as peer dependencies. These are typically already installed in React projects.
100
+
97
101
  ### Compatibility
98
102
 
99
103
  Routerino supports:
100
104
 
101
- - **React 18 and 19** - Both versions are tested and supported
102
- - **Preact** - Compatible via `@preact/compat`
103
- - **Node.js 18+** - Tested on Node.js 18, 20, 22, and 24. Could be used on earlier versions if we skip tests
105
+ - React 18 and 19: Both versions are tested and supported. React 17 works for CSR-only.
106
+ - Node.js 18+: Tested on Node.js 18, 20, 22, and 24. Could be used on earlier versions if we skip tests.
107
+ - Preact: Compatible via `@preact/compat`. See [using Preact](#using-preact) below.
104
108
 
105
109
  ## Usage
106
110
 
107
111
  Here's a short example of using Routerino in your React application:
108
112
 
109
113
  ```jsx
114
+ export const routes = [
115
+ {
116
+ path: "/",
117
+ element: <p>This is the home page!</p>,
118
+ title: "My Home Page!",
119
+ description: "Welcome to my home page!",
120
+ },
121
+ ];
110
122
  <Routerino
111
123
  title="Example.com"
112
- routes={[
113
- {
114
- path: "/",
115
- element: <p>This is the home page!</p>,
116
- title: "My Home Page!",
117
- description: "Welcome to my home page!",
118
- },
119
- ]}
120
- debug={window.location.host.includes("localhost:")}
121
- />
124
+ routes={routes}
125
+ debug={window.location.hostname === "localhost"}
126
+ />;
127
+ ```
128
+
129
+ Links are just standard HTML anchor tags. No need to use special `<Link>` components—you can use whatever components or design system you like. For example: <a href="/some-page/">a link</a> is perfectly valid. This is very handy for markdown-based content. With standard link support in Routerino, you won't need to [transform your markdown content with custom React components](https://github.com/remarkjs/react-markdown?tab=readme-ov-file#appendix-b-components). Routerino handles same-origin anchor clicks. Cross-origin links and non-HTTP schemes (e.g., mailto:, tel:) are handled by the browser as usual.
130
+
131
+ ### Programmatic Navigation
132
+
133
+ <details>
134
+ <summary style="cursor:pointer;font-style:italic">Navigate programmatically using standard browser APIs (expand for details)</summary>
135
+
136
+ ```js
137
+ // Using History API
138
+ window.history.pushState({}, "", "/about/");
139
+ window.dispatchEvent(new PopStateEvent("popstate"));
140
+
141
+ // Working with URL and search params
142
+ const url = new URL(window.location);
143
+ url.searchParams.set("page", "2");
144
+ window.history.pushState({}, "", url.toString());
145
+ window.dispatchEvent(new PopStateEvent("popstate"));
122
146
  ```
123
147
 
124
- Links are just regular HTML anchor tags. No need to use special `<Link>` components and you can handle styling however you wish. For example: `<a href="/some-page/">a link</a>`
148
+ </details>
125
149
 
126
150
  See [props](#props-arguments) for full explanations and [example code](#how-to-guides--example-code) for more complete code samples.
127
151
 
152
+ ### Using Preact
153
+
154
+ Routerino is fully compatible with Preact via the `@preact/compat` compatibility layer. This allows you to use Routerino in Preact projects with the same API.
155
+
156
+ #### Setup Instructions
157
+
158
+ 1. Install Preact and the compatibility layer:
159
+
160
+ ```sh
161
+ npm i preact @preact/compat
162
+ ```
163
+
164
+ 2. Configure your bundler to alias React to @preact/compat:
165
+
166
+ **Vite Configuration:**
167
+
168
+ ```js
169
+ // vite.config.js
170
+ import { defineConfig } from "vite";
171
+ import preact from "@preact/preset-vite";
172
+
173
+ export default defineConfig({
174
+ plugins: [preact()],
175
+ resolve: {
176
+ alias: {
177
+ react: "@preact/compat",
178
+ "react-dom": "@preact/compat",
179
+ "react/jsx-runtime": "@preact/compat/jsx-runtime",
180
+ },
181
+ },
182
+ });
183
+ ```
184
+
185
+ **Webpack Configuration:**
186
+
187
+ ```js
188
+ // webpack.config.js
189
+ module.exports = {
190
+ resolve: {
191
+ alias: {
192
+ react: "preact/compat",
193
+ "react-dom": "preact/compat",
194
+ },
195
+ },
196
+ };
197
+ ```
198
+
199
+ 3. Use Routerino exactly as you would in a React project - the API is identical!
200
+
128
201
  ### Props (arguments)
129
202
 
130
- Please see the [default props](#default-props) and [usage](#usage) sections for more details.
203
+ The table below shows all available props with their default values. See the [usage](#usage) section for examples.
131
204
 
132
205
  #### Routerino props
133
206
 
@@ -143,13 +216,11 @@ All of these are optional, so it's easy to get started with nothing but a bare-b
143
216
  | [errorTemplate](#errortemplate-element) | React.ReactNode | Error page template | `<DefaultErrorTemplate />` |
144
217
  | [errorTitle](#errortitle-string) | string | Error page title | `"Page error [500]"` |
145
218
  | [useTrailingSlash](#usetrailingslash-bool) | boolean | Use trailing slashes in URLs | `true` |
146
- | [usePrerenderTags](#useprerendertags-bool) | boolean | Use pre-render meta tags | `true` |
219
+ | [usePrerenderTags](#useprerendertags-bool) | boolean | Use pre-render meta tags | `false` |
147
220
  | [baseUrl](#baseurl-string) | string | Base URL for canonical tags | `null` (uses window.location) |
148
221
  | [imageUrl](#imageurl-string) | string | Default image URL for sharing | `null` |
149
222
  | [touchIconUrl](#touchiconurl-string) | string | Image URL for PWA homescreen icon | `null` |
150
223
  | [debug](#debug-boolean) | boolean | Enable debug mode | `false` |
151
- | [titlePrefix](#titleprefix-string) | string | Deprecated: Title prefix | `""` |
152
- | [titlePostfix](#titlepostfix-string) | string | Deprecated: Title postfix | `""` |
153
224
 
154
225
  ##### `title`: string;
155
226
 
@@ -220,30 +291,18 @@ Default: `true`
220
291
 
221
292
  ##### `usePrerenderTags`: bool;
222
293
 
223
- Include meta tags to enable proper error codes like 404 when serving pages to a search crawler.
294
+ Include prerender-specific meta tags to enable proper error codes like 404 when serving prerendered pages to a search crawler. This means that Routerino uses <meta name="prerender-status-code" content="..."> and <meta name="prerender-header" content="Location: ..."> to signal status codes and redirects to prerender servers.
224
295
 
225
- Default: `true`
296
+ Default: `false`
226
297
 
227
298
  ##### `baseUrl`: string;
228
299
 
229
300
  The base URL to use for canonical tags and og:url meta tags. If not provided, uses `window.location.origin`. This is useful when you want to specify the production URL regardless of the current environment.
230
301
 
231
- Example: `"https://example.com"`
302
+ **Important:** The baseUrl must NOT end with a trailing slash (`/`). Correct example: `"https://example.com"`
232
303
 
233
304
  Default: `null` (uses window.location.origin)
234
305
 
235
- ##### `titlePrefix`: string;
236
-
237
- Deprecated: use `title` instead. A string to preprend to every title. Should include the brand name, a separator, and spacing, such as `Example.com | `<- Note the extra end space.
238
-
239
- Default: `""` (empty string)
240
-
241
- ##### `titlePostfix`: string;
242
-
243
- Deprecated: use `title` instead. A string to append to every title. Should include the brand name, a separator, and spacing, such as the following example. Note the extra starting space ->` - Example.com`.
244
-
245
- Default: `""` (empty string)
246
-
247
306
  ##### `imageUrl`: string;
248
307
 
249
308
  A string containing the path of the default (site-wide) image to use for sharing previews.
@@ -258,7 +317,21 @@ Default: `null`
258
317
 
259
318
  ##### `debug`: boolean;
260
319
 
261
- Enable debug mode for additional logging and information.
320
+ Enable debug mode for additional logging and information. When enabled, Routerino logs detailed information to the console including:
321
+
322
+ - Route changes and pattern matching
323
+ - Meta tag updates
324
+ - Error boundaries and component failures
325
+ - Performance timing information
326
+
327
+ Example debug output:
328
+
329
+ ```
330
+ [Routerino] Route changed to: /products/laptop/
331
+ [Routerino] Matched pattern: /products/:id/
332
+ [Routerino] Route params: { id: "laptop" }
333
+ [Routerino] Updated meta tag: og:title = "Laptop Pro - $1299"
334
+ ```
262
335
 
263
336
  Default: `false`
264
337
 
@@ -268,7 +341,7 @@ There is a default RouteConfig that will be loaded if you don't specify any rout
268
341
 
269
342
  ##### path: string;
270
343
 
271
- The path of the desired route. **Must start with a forward slash (`/`)**. For example: `"/foo/"`, or `"/about/"`.
344
+ The path of the desired route. **Must start with a forward slash (`/`)**. For example: `"/foo/"`, or `"/about/"`. Supported dynamic segments use the :param syntax (e.g., /products/:id/). Wildcards, optional segments, and splats are not supported. Params are matched by position; decode values in your components if needed.
272
345
 
273
346
  ##### element: React.ReactNode;
274
347
 
@@ -286,14 +359,6 @@ The page's description, which will show up on search results pages.
286
359
 
287
360
  Any desired head tags for that route. See [HeadTag props](#headtag-props) for details.
288
361
 
289
- ##### titlePrefix?: string;
290
-
291
- Deprecated: a title prefix for this route to override the default. Use title and separator instead.
292
-
293
- ##### titlePostfix?: string;
294
-
295
- Deprecated: a title postfix for this route to override the default. Use title and separator instead.
296
-
297
362
  ##### imageUrl?: string;
298
363
 
299
364
  An image URL to set for the page's og:image tag.
@@ -334,14 +399,39 @@ An array of HeadTag objects that can be added to the route to manage meta tags,
334
399
 
335
400
  - `target` (string): The "target" attribute of the tag. Defines where to open the linked resource.
336
401
 
337
- ### Get route parameters and the current route, and updating head tags
402
+ ### Using the `useRouterino` Hook
403
+
404
+ The `useRouterino` hook provides access to router state from any child component. This is the primary way to access route information and update head tags dynamically.
405
+
406
+ ```jsx
407
+ import { useRouterino } from "routerino";
408
+
409
+ function MyComponent() {
410
+ const { currentRoute, params, routePattern, updateHeadTag } = useRouterino();
338
411
 
339
- Child components can access the current route and its parameters via the `Routerino` or `routerino` props. This prop is an object with the following properties:
412
+ // Access current route information
413
+ console.log("Current path:", currentRoute); // e.g., "/products/laptop/"
414
+ console.log("Route pattern:", routePattern); // e.g., "/products/:id/"
415
+ console.log("Route params:", params); // e.g., { id: "laptop" }
340
416
 
341
- - routePattern: The current route path pattern, such as `/foo/:id/`.
342
- - currentRoute: The current route path, such as `/foo/bar/`.
343
- - params: a dictionary of route parameters, such as `{id: "bar"}`. These will match the route pattern provided by the `path` prop.
344
- - updateHeadTag: a function that takes a [HeadTag object](#headtag-props) and updates the head tags for the current route. This is useful for setting custom `<head>` child tags for each route, such as Open Graph tags for social previews. You may need to set this after doing some data fetches, for example. See the next section for more details.
417
+ // Update meta tags dynamically
418
+ useEffect(() => {
419
+ updateHeadTag({
420
+ name: "description",
421
+ content: `Product page for ${params.id}`,
422
+ });
423
+ }, [params.id]);
424
+
425
+ return <div>Product: {params.id}</div>;
426
+ }
427
+ ```
428
+
429
+ #### Hook Return Values
430
+
431
+ - **`currentRoute`**: The current URL path (e.g., `/foo/bar/`)
432
+ - **`routePattern`**: The matched route pattern with parameters (e.g., `/foo/:id/`)
433
+ - **`params`**: Object containing route parameters (e.g., `{id: "bar"}`)
434
+ - **`updateHeadTag`**: Function to dynamically update head tags (see [updateHeadTag](#updateheadtag) section)
345
435
 
346
436
  ### `updateHeadTag`
347
437
 
@@ -349,11 +439,42 @@ The `updateHeadTag` function is responsible for creating or updating the specifi
349
439
 
350
440
  Please note that the `updateHeadTag` function requires at least one attribute to be provided. If no attributes are specified, an error message will be logged.
351
441
 
352
- ### Props
442
+ #### Props
353
443
 
354
444
  See [HeadTag props](#headtag-props) for arguments and some common tag attributes.
355
445
 
356
- #### Examples
446
+ ## TypeScript Support
447
+
448
+ Routerino includes TypeScript definitions out of the box. The types are automatically available when you import Routerino.
449
+
450
+ ### Basic Usage
451
+
452
+ ```typescript
453
+ // Both import styles are supported
454
+ import Routerino from 'routerino'; // Default import
455
+ // or
456
+ import { Routerino } from 'routerino'; // Named import (recommended)
457
+
458
+ import type { RouteConfig } from 'routerino';
459
+
460
+ export const routes: RouteConfig[] = [
461
+ {
462
+ path: '/',
463
+ element: <HomePage />,
464
+ title: 'Home',
465
+ description: 'Welcome to our site'
466
+ }
467
+ ];
468
+
469
+ // TypeScript will validate all props
470
+ <Routerino
471
+ routes={routes}
472
+ baseUrl="https://example.com"
473
+ title="My Site"
474
+ />
475
+ ```
476
+
477
+ ## Examples
357
478
 
358
479
  Setting a page description:
359
480
 
@@ -379,32 +500,144 @@ To optimize your site for SEO and social previews when using Routerino, consider
379
500
 
380
501
  ### Page Titles
381
502
 
382
- - Keep page titles unique for each route. Avoid including the site title (e.g., "Foo.com") in individual page titles.
503
+ - Keep page titles unique for each route. Avoid including the site name like "Foo.com" in individual page titles, Routerino adds that automatically.
383
504
  - Aim for concise, descriptive titles that accurately represent the page content.
384
- - Ideal title length is typically 50-60 characters.
505
+ - We prefer to keep title length at a max of 50-60 characters, longer text will be ignored or cut off (especially for mobile users).
506
+
507
+ ### URL Structure & Canonicalization
508
+
509
+ #### Canonical URLs (Don't worry, this is handled for you!)
510
+
511
+ **What is canonicalization?**
385
512
 
386
- ### URL Structure
513
+ When multiple URLs show the same content (like `/about` vs `/about/`), search engines need to know which one is the "official" version to avoid duplicate content penalties. The canonical URL tells search engines which version to index and rank.
387
514
 
388
- - Maintain trailing slash consistency: Search engines treat `example.com/foo` and `example.com/foo/` as different pages.
389
- - Routerino will render the same content for both versions to avoid user errors.
390
- - Routerino will use the `useTrailingSlash` prop to automatically set the preferred URL version for search engines.
515
+ **How Routerino handles this automatically**
516
+
517
+ - Sets `<link rel="canonical">` tags on every page pointing to the preferred URL version
518
+ - Uses the `useTrailingSlash` prop (default: `true`) to determine the canonical format
519
+ - Generates proper `og:url` meta tags for social sharing
520
+ - For SSG: Creates both file versions (`/about.html` and `/about/index.html`) with canonical tags
521
+ - For Prerender: Includes meta tags that instruct the prerender server to return 301 redirects
522
+ - Ensures sitemap.xml only contains canonical URLs
523
+
524
+ **Best practices that Routerino implements**
525
+
526
+ - Consistency: Sitemap, canonical, and redirects all agree
527
+ - Dual file generation (SSG): Creates both `/about.html` and `/about/index.html` so both URLs work
528
+ - Prerender support (SPA): Includes meta tags that tell prerender services to serve proper status codes
529
+ - Absolute URLs: When `baseUrl` is provided, canonical tags use the provided base instead of defaulting to the current domain
530
+
531
+ **Example of what's generated**
532
+
533
+ With Static Site Generation (SSG):
534
+
535
+ ```html
536
+ <!-- Both /about.html and /about/index.html contain: -->
537
+ <link rel="canonical" href="https://example.com/about/" />
538
+ <meta property="og:url" content="https://example.com/about/" />
539
+ ```
540
+
541
+ With Prerender (SPA):
542
+
543
+ ```html
544
+ <!-- For canonical URL (/about/) -->
545
+ <link rel="canonical" href="https://example.com/about/" />
546
+ <meta property="og:url" content="https://example.com/about/" />
547
+
548
+ <!-- For non-canonical URL (/about) -->
549
+ <meta name="prerender-status-code" content="301" />
550
+ <meta name="prerender-header" content="Location: https://example.com/about/" />
551
+ ```
552
+
553
+ You can override the default with `useTrailingSlash={false}` if you prefer URLs without trailing slashes. Either way, Routerino ensures search engines see consistent, canonical URLs.
391
554
 
392
555
  ### Sitemap Generation
393
556
 
394
557
  - Automate the creation of `sitemap.xml` and `robots.txt` during your build process with Routerino Forge.
395
558
  - The Vite plugin automatically generates these files when building static sites (see [Static Site Generation](#static-site-generation)).
396
559
 
397
- ### Social Previews
560
+ ### Social Previews & Open Graph
561
+
562
+ Routerino automatically sets the core Open Graph tags (`og:title`, `og:description`, `og:url`, `og:image`) for every page. Here's how to optimize them:
563
+
564
+ #### Image Best Practices
565
+
566
+ - Size: Use 1200×630 pixels (1.91:1 ratio) for maximum compatibility
567
+ - Content: Avoid text in images - use metadata for text instead (per Apple's guidelines)
568
+ - Add dimensions for faster first-share rendering:
569
+ ```js
570
+ // In your route's component or after data loading
571
+ updateHeadTag({ property: "og:image:width", content: "1200" });
572
+ updateHeadTag({ property: "og:image:height", content: "630" });
573
+ ```
574
+
575
+ #### Title & Branding
576
+
577
+ - Don't duplicate branding in `og:title` - Routerino uses your page title directly
578
+ - For site-wide branding, add `og:site_name` instead:
579
+ ```js
580
+ updateHeadTag({ property: "og:site_name", content: "Your Brand" });
581
+ ```
582
+
583
+ #### Platform-Specific Enhancements
584
+
585
+ - Apple/iMessage: Set `touchIconUrl` prop for iMessage link previews
586
+ - Video content: Add direct playable assets when possible:
587
+ ```js
588
+ updateHeadTag({
589
+ property: "og:video",
590
+ content: "https://example.com/video.mp4",
591
+ });
592
+ updateHeadTag({ property: "og:video:type", content: "video/mp4" });
593
+ ```
594
+
595
+ #### Testing Your Previews
596
+
597
+ Test how your links appear on different platforms:
598
+
599
+ - [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) - Use "Scrape Again" after changes
600
+ - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/)
601
+ - [Twitter Card Validator](https://cards-dev.twitter.com/validator)
602
+
603
+ **Tip**: Social platforms cache previews aggressively. After updating tags, use each platform's debugger to force a refresh.
604
+
605
+ #### Twitter Card Tags (Handled Automatically)
398
606
 
399
- - Add an `imageUrl` to each route for page-specific social preview images.
400
- - Set a default `imageUrl` via the Routerino prop for pages without unique images.
401
- - Ensure preview images meet platform-specific size requirements (e.g., 1200x630 pixels for Facebook).
402
- - Include any other Open Graph tags you need for each page with the `updateHeadTag` function.
607
+ Routerino automatically includes Twitter card meta tags to ensure rich previews when your links are shared on Twitter/X:
608
+
609
+ ```html
610
+ <meta name="twitter:card" content="summary_large_image" />
611
+ ```
612
+
613
+ **What this does:**
614
+
615
+ - Without Twitter cards: Links appear as plain URLs with no preview
616
+ - With Twitter cards: Links show rich previews with title, description, and image
617
+
618
+ **How it works:**
619
+
620
+ - Routerino always sets `summary_large_image` for maximum engagement
621
+ - If you provide an `imageUrl`, Twitter displays a large prominent image
622
+ - If no image is provided, Twitter gracefully falls back to a text-only card
623
+ - This is better than no card at all (which would show just the bare URL)
624
+
625
+ **The complete picture:**
626
+
627
+ ```html
628
+ <!-- What Routerino generates for social sharing -->
629
+ <meta name="twitter:card" content="summary_large_image" />
630
+ <meta property="og:title" content="Your Page Title" />
631
+ <meta property="og:description" content="Your page description" />
632
+ <meta property="og:image" content="https://example.com/preview.jpg" />
633
+ ```
634
+
635
+ This creates an engaging Twitter preview with a large image, title, and description - much more clickable than a plain URL.
403
636
 
404
637
  ### Meta Descriptions
405
638
 
406
639
  - Provide unique, informative descriptions for each route.
407
- - Keep descriptions between 100-200 characters. This range is optimal for most search engines.
640
+ - Descriptions may be used for snippets so keep them short and to the point.
408
641
  - Descriptions longer than ~150 characters may be truncated in search results.
409
642
 
410
643
  ### Additional SEO Considerations
@@ -419,10 +652,10 @@ By following these practices, you'll improve your site's SEO performance and soc
419
652
 
420
653
  When using Routerino Forge for static site generation, `sitemap.xml` and `robots.txt` files are automatically generated during the build process:
421
654
 
422
- - **sitemap.xml**: Contains all static routes (dynamic routes with parameters like `:id` are excluded)
423
- - **robots.txt**: Created with a reference to the sitemap (if it doesn't already exist)
655
+ - sitemap.xml: Contains all static routes (dynamic routes with parameters like `:id` are excluded)
656
+ - robots.txt: Created with a reference to the sitemap (if it doesn't already exist)
424
657
 
425
- These files are generated automatically when you build with the Routerino Forge Vite plugin - no additional configuration needed!
658
+ These files are generated automatically when you build with the Routerino Forge Vite plugin - no additional configuration needed.
426
659
 
427
660
  ### Sample Build Output:
428
661
 
@@ -450,7 +683,7 @@ export default defineConfig({
450
683
  plugins: [
451
684
  react(),
452
685
  routerinoForge({
453
- baseUrl: "https://example.com", // Your production URL
686
+ baseUrl: "https://example.com", // Your production URL (no trailing slash)
454
687
  // Optional settings (these are the defaults):
455
688
  // routes: "./src/routes.jsx", // Your routes file
456
689
  // outputDir: "dist",
@@ -458,6 +691,8 @@ export default defineConfig({
458
691
  // prerenderStatusCode: true,
459
692
  // useTrailingSlash: true, // Set to false for /about instead of /about/
460
693
  // verbose: false,
694
+ // ssgCacheDir: "node_modules/.cache/routerino-forge", // SSG cache directory
695
+ // optimizeImages: true, // Enable image optimization (see below)
461
696
  }),
462
697
  ],
463
698
  });
@@ -466,7 +701,7 @@ export default defineConfig({
466
701
  **Requirements:**
467
702
 
468
703
  - Your `index.html` must have `<div id="root"></div>` (standard for all React apps)
469
- - Routes must be exported from your routes file (see below)
704
+ - **IMPORTANT**: Routes must be exported (not defined inline) for SSG to work
470
705
 
471
706
  **Features:**
472
707
 
@@ -476,24 +711,139 @@ export default defineConfig({
476
711
  - `/about/` → `about/index.html`
477
712
  - Canonical URLs and redirects based on `useTrailingSlash` setting
478
713
  - Prerender compatible 301 redirects for non-canonical versions
714
+ - **Static host ready**: Output format aligns perfectly with Vercel, Netlify, and Cloudflare Pages conventions
715
+ - Routes generate `/path/index.html` for clean URLs (and `/path.html` for compatibility with no-slash URLs)
716
+ - `404.html` at root for custom error pages
717
+ - No server configuration needed - just deploy!
479
718
  - Automatic canonical URL and og:url meta tags
480
719
  - Injects rendered HTML into your root div
481
720
  - Generates sitemap.xml and robots.txt
482
721
  - Creates a 404.html page
483
722
  - Skips dynamic routes (with `:param` syntax)
484
723
  - SEO optimized: Complete HTML with meta tags
724
+ - Image optimization: Automatic blur placeholders (LQIP)
485
725
  - Easy configuration: Works out of the box with Vite and minimal setup
486
726
 
727
+ #### Image Optimization
728
+
729
+ Routerino Forge can automatically optimize images in your static builds for faster loading:
730
+
731
+ ```js
732
+ routerinoForge({
733
+ optimizeImages: true, // Enable with defaults
734
+ // Or configure in detail:
735
+ optimizeImages: {
736
+ enabled: true,
737
+ placeholderSize: 20, // Size of blur placeholder (20x20 pixels)
738
+ blur: 4, // Blur radius for placeholder
739
+ maxSize: 10485760, // Maximum image size to process (10MB)
740
+ minSize: 1024, // Minimum image size to process (1KB)
741
+ cacheDir: "node_modules/.cache/routerino-forge", // Cache directory
742
+ },
743
+ });
744
+ ```
745
+
746
+ **What it does:**
747
+
748
+ - Generates tiny blur placeholders for images (base64 encoded)
749
+ - Caches processed images to speed up subsequent builds
750
+ - Preserves original images while enhancing loading performance
751
+ - Skips external images (http/https), data URIs, and SVGs
752
+
753
+ **Note:** Image optimization requires `ffmpeg` to be installed. Without it, images work normally but without blur placeholders. Install with `brew install ffmpeg` (Mac), `apt install ffmpeg` (Ubuntu), or `choco install ffmpeg` (Windows).
754
+
755
+ ##### CI/CD Setup for Image Optimization
756
+
757
+ To enable image optimization in your CI/CD pipeline, you need to install ffmpeg. Here are examples for the most common setups:
758
+
759
+ ###### GitHub Actions
760
+
761
+ Add the `setup-ffmpeg` action to your workflow:
762
+
763
+ ```yaml
764
+ name: Build and Deploy
765
+
766
+ on:
767
+ push:
768
+ branches: [main]
769
+
770
+ jobs:
771
+ build:
772
+ runs-on: ubuntu-latest
773
+
774
+ steps:
775
+ - uses: actions/checkout@v4
776
+
777
+ - uses: actions/setup-node@v4
778
+ with:
779
+ node-version: "20"
780
+ cache: "npm"
781
+
782
+ # Install ffmpeg for image optimization
783
+ - name: Setup FFmpeg
784
+ uses: FedericoCarboni/setup-ffmpeg@v3
785
+ with:
786
+ ffmpeg-version: release
787
+
788
+ - run: npm ci
789
+ - run: npm run build
790
+
791
+ # Deploy to GitHub Pages (optional)
792
+ - uses: peaceiris/actions-gh-pages@v3
793
+ if: github.ref == 'refs/heads/main'
794
+ with:
795
+ github_token: ${{ secrets.GITHUB_TOKEN }}
796
+ publish_dir: ./dist
797
+ ```
798
+
799
+ ###### Docker
800
+
801
+ For containerized deployments, install ffmpeg in your Dockerfile:
802
+
803
+ ```dockerfile
804
+ FROM node:20-alpine
805
+
806
+ # Install ffmpeg
807
+ RUN apk add --no-cache ffmpeg
808
+
809
+ WORKDIR /app
810
+
811
+ # Copy package files
812
+ COPY package*.json ./
813
+ RUN npm ci
814
+
815
+ # Copy source and build
816
+ COPY . .
817
+ RUN npm run build
818
+
819
+ # Production stage with nginx
820
+ FROM nginx:alpine
821
+ COPY --from=0 /app/dist /usr/share/nginx/html
822
+ ```
823
+
824
+ ###### Netlify
825
+
826
+ Netlify builds run on Ubuntu images, so you can install ffmpeg using apt-get in your build command.
827
+
828
+ In your netlify.toml:
829
+
830
+ ```
831
+ [build]
832
+ command = "apt-get update && apt-get install -y ffmpeg && npm ci && npm run build"
833
+ publish = "dist"
834
+ ```
835
+
487
836
  #### Routes Configuration
488
837
 
489
- Define routes with `element` property containing JSX elements:
838
+ **Critical for SSG**: Routes MUST be exported for the build plugin to discover them. The plugin needs to import your routes at build time, so inline route definitions won't work.
839
+
840
+ Define routes with an `element` property containing JSX elements:
490
841
 
491
842
  ```jsx
492
- // src/routes.jsx
493
843
  export const routes = [
494
844
  {
495
845
  path: "/",
496
- element: <HomePage featured="Latest News" />, // Props preserved!
846
+ element: <HomePage featured="Latest News" />, // Props preserved
497
847
  title: "Home - My Site",
498
848
  description: "Welcome to our site",
499
849
  imageUrl: "/images/home-og.jpg", // Optional social image
@@ -520,14 +870,97 @@ export const notFoundTemplate = (
520
870
  );
521
871
  ```
522
872
 
873
+ #### Generating Routes from Data (e.g., Product Listings)
874
+
875
+ You can dynamically generate routes from data sources at build time. This is perfect for creating SEO-friendly static pages for products, blog posts, or any content from APIs or databases:
876
+
877
+ ```jsx
878
+ // src/routes.jsx
879
+ import { ProductPage } from "./components/ProductPage";
880
+ import { BlogPost } from "./components/BlogPost";
881
+
882
+ // This could be fetched from an API at build time
883
+ const products = [
884
+ {
885
+ id: "laptop-pro",
886
+ name: "Laptop Pro",
887
+ price: 1299,
888
+ description: "High-performance laptop",
889
+ },
890
+ {
891
+ id: "wireless-mouse",
892
+ name: "Wireless Mouse",
893
+ price: 49,
894
+ description: "Ergonomic wireless mouse",
895
+ },
896
+ { id: "usb-hub", name: "USB Hub", price: 29, description: "7-port USB hub" },
897
+ ];
898
+
899
+ // Generate a route for each product
900
+ const productRoutes = products.map((product) => ({
901
+ path: `/products/${product.id}/`,
902
+ element: <ProductPage product={product} />,
903
+ title: `${product.name} - $${product.price}`,
904
+ description: product.description,
905
+ tags: [
906
+ { property: "og:type", content: "product" },
907
+ { property: "product:price:amount", content: product.price.toString() },
908
+ { property: "product:price:currency", content: "USD" },
909
+ ],
910
+ }));
911
+
912
+ // You can also fetch data asynchronously at build time
913
+ const blogPosts = await fetch("https://api.example.com/posts")
914
+ .then((res) => res.json())
915
+ .catch(() => []); // Fallback to empty array if API fails
916
+
917
+ const blogRoutes = blogPosts.map((post) => ({
918
+ path: `/blog/${post.slug}/`,
919
+ element: <BlogPost post={post} />,
920
+ title: post.title,
921
+ description: post.excerpt,
922
+ imageUrl: post.featuredImage,
923
+ tags: [
924
+ { property: "og:type", content: "article" },
925
+ { property: "article:published_time", content: post.publishedAt },
926
+ { property: "article:author", content: post.author },
927
+ ],
928
+ }));
929
+
930
+ // Combine all routes
931
+ export const routes = [
932
+ {
933
+ path: "/",
934
+ element: <HomePage />,
935
+ title: "Home",
936
+ description: "Welcome to our store",
937
+ },
938
+ ...productRoutes, // All products get their own static pages
939
+ ...blogRoutes, // All blog posts get their own static pages
940
+ {
941
+ path: "/products/",
942
+ element: <ProductListing products={products} />,
943
+ title: "All Products",
944
+ description: "Browse our product catalog",
945
+ },
946
+ ];
947
+ ```
948
+
949
+ **Benefits of this approach:**
950
+
951
+ - Each product/post gets its own static HTML page with proper SEO meta tags
952
+ - All generated routes are automatically included in `sitemap.xml`
953
+ - Search engines can discover and index every product/post
954
+ - Fast page loads since HTML is pre-rendered at build time
955
+ - Type-safe if using TypeScript with your data structures
956
+
523
957
  ### What Gets Generated
524
958
 
525
959
  The static build process will:
526
960
 
527
- - Generate an HTML file for each static route (e.g., `/about/` → `about/index.html`)
961
+ - Generate HTML files for each static route (e.g., `/about/` → `about/index.html` & `about.html`)
528
962
  - Skip dynamic routes with parameters (e.g., `/user/:id`)
529
- - Apply route-specific meta tags (title, description, og:image, keywords)
530
- - Add proper `data-route` attributes for client-side hydration
963
+ - Apply route-specific meta tags (title, description, og:image)
531
964
  - Generate `sitemap.xml` with all static routes (Vite plugin only)
532
965
  - Preserve your existing HTML structure and assets
533
966
 
@@ -597,15 +1030,15 @@ npm install
597
1030
 
598
1031
  This command will read the `package.json` file in your project and install all the necessary dependencies.
599
1032
 
600
- 5. Now, add Routerino to your project as a development dependency:
1033
+ 5. Now, add Routerino to your project as a dependency:
601
1034
 
602
1035
  ```
603
1036
 
604
- npm install routerino --save-dev
1037
+ npm install routerino
605
1038
 
606
1039
  ```
607
1040
 
608
- This command will install the latest version of Routerino and save it to your `package.json` file under the `devDependencies` section.
1041
+ This command will install the latest version of Routerino and save it to your `package.json` file under the `dependencies` section.
609
1042
 
610
1043
  With these steps, you'll have a new React project set up with Vite as the build tool and Routerino installed as a development dependency. You can now start building your application with React & Routerino.
611
1044
 
@@ -615,9 +1048,38 @@ This example includes the full React configuration. It might take the place of `
615
1048
 
616
1049
  ```jsx
617
1050
  import React from "react";
618
- import { render } from "react-dom";
1051
+ import { createRoot } from "react-dom/client";
619
1052
  import Routerino from "routerino";
620
1053
 
1054
+ export const routes = [
1055
+ {
1056
+ path: "/",
1057
+ element: <p>Welcome to Home</p>,
1058
+ title: "Home",
1059
+ description: "Welcome to my website!",
1060
+ },
1061
+ {
1062
+ path: "/about/",
1063
+ element: <p>About us...</p>,
1064
+ title: "About",
1065
+ description: "Learn more about us.",
1066
+ },
1067
+ {
1068
+ path: "/contact/",
1069
+ element: (
1070
+ <div>
1071
+ <h1>Contact Us</h1>
1072
+ <p>
1073
+ Please <a href="mailto:user@example.com">send us an email</a> at
1074
+ user@example.com
1075
+ </p>
1076
+ </div>
1077
+ ),
1078
+ title: "Contact",
1079
+ description: "Get in touch with us.",
1080
+ },
1081
+ ];
1082
+
621
1083
  const App = () => (
622
1084
  <main>
623
1085
  <nav>
@@ -628,34 +1090,7 @@ const App = () => (
628
1090
  title="Example.com"
629
1091
  notFoundTitle="Sorry, but this page does not exist."
630
1092
  errorTitle="Yikes! Something went wrong."
631
- routes={[
632
- {
633
- path: "/",
634
- element: <p>Welcome to Home</p>,
635
- title: "Home",
636
- description: "Welcome to my website!",
637
- },
638
- {
639
- path: "/about/",
640
- element: <p>About us...</p>,
641
- title: "About",
642
- description: "Learn more about us.",
643
- },
644
- {
645
- path: "/contact/",
646
- element: (
647
- <div>
648
- <h1>Contact Us</h1>
649
- <p>
650
- Please <a href="mailto:user@example.com">send us an email</a> at
651
- user@example.com
652
- </p>
653
- </div>
654
- ),
655
- title: "Contact",
656
- description: "Get in touch with us.",
657
- },
658
- ]}
1093
+ routes={routes}
659
1094
  />
660
1095
 
661
1096
  <footer>
@@ -667,7 +1102,7 @@ const App = () => (
667
1102
  </main>
668
1103
  );
669
1104
 
670
- render(<App />, document.getElementById("root"));
1105
+ createRoot(document.getElementById("root")).render(<App />);
671
1106
  ```
672
1107
 
673
1108
  ## ErrorBoundary Component
@@ -686,7 +1121,7 @@ import { ErrorBoundary } from "routerino";
686
1121
  <ErrorBoundary
687
1122
  fallback={<div>Something went wrong. Please try again.</div>}
688
1123
  errorTitleString="Error | My Application"
689
- usePrerenderTags={true}
1124
+ debug={window.location.hostname === "localhost"} // Auto-enable on localhost
690
1125
  >
691
1126
  <MyComponent />
692
1127
  </ErrorBoundary>
@@ -694,20 +1129,42 @@ import { ErrorBoundary } from "routerino";
694
1129
 
695
1130
  ### Props
696
1131
 
697
- | Prop | Type | Required | Description |
698
- | ------------------ | ----------- | -------- | ---------------------------------------------------- |
699
- | `children` | `ReactNode` | No | The child components to render when there's no error |
700
- | `fallback` | `ReactNode` | No | The UI to display when an error is caught |
701
- | `errorTitleString` | `string` | Yes | The document title to set when an error occurs |
702
- | `usePrerenderTags` | `boolean` | No | Whether to set prerender meta tag (status code 500) |
1132
+ | Prop | Type | Required | Description |
1133
+ | ------------------ | ----------- | -------- | ----------------------------------------------------------------- |
1134
+ | `children` | `ReactNode` | No | The child components to render when there's no error |
1135
+ | `fallback` | `ReactNode` | No | The UI to display when an error is caught |
1136
+ | `errorTitleString` | `string` | Yes | The document title to set when an error occurs |
1137
+ | `usePrerenderTags` | `boolean` | No | Whether to set prerender meta tags (status code 500) |
1138
+ | `routePath` | `string` | No | The current route path for better error context (used internally) |
1139
+ | `debug` | `boolean` | No | Enable detailed console logging of errors (default: `false`) |
1140
+
1141
+ ### Debug Logging
1142
+
1143
+ When `debug` is set to `true`, the ErrorBoundary provides detailed console output:
1144
+
1145
+ - Groups error information for better readability
1146
+ - Logs the component stack trace showing exactly where the error occurred
1147
+ - Shows the failed route path (if available)
1148
+ - Includes timestamp of when the error occurred
1149
+
1150
+ **Recommended debug pattern:**
1151
+
1152
+ ```jsx
1153
+ // Auto-enable debug mode on localhost
1154
+ debug={window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"}
1155
+
1156
+ // Or use environment variable
1157
+ debug={process.env.NODE_ENV === "development"}
1158
+ ```
703
1159
 
704
1160
  ### Features
705
1161
 
706
1162
  - Catches JavaScript errors in child component tree
707
1163
  - Displays fallback UI instead of white screen
708
1164
  - Sets document title on error
709
- - Logs errors to console for debugging
1165
+ - Provides detailed debug logging when enabled
710
1166
  - Optionally sets prerender status code for SEO
1167
+ - Used internally by Routerino to protect route components
711
1168
 
712
1169
  This is the same error boundary used internally by Routerino to protect your route components from crashing the entire application.
713
1170
 
@@ -734,10 +1191,10 @@ your-project/
734
1191
 
735
1192
  ```jsx
736
1193
  // Before (importing from the package)
737
- import { Router } from "routerino";
1194
+ import Routerino from "routerino";
738
1195
 
739
1196
  // After (importing from the vendored file)
740
- import { Router } from "./vendor/routerino";
1197
+ import Routerino from "./vendor/routerino";
741
1198
  ```
742
1199
 
743
1200
  4. You're all set! Routerino is now vendored in your project, and you can use it as before.