routerino 2.2.1 → 2.2.2-rc1

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
@@ -683,7 +683,7 @@ export default defineConfig({
683
683
  routerinoForge({
684
684
  baseUrl: "https://example.com", // Your production URL (no trailing slash)
685
685
  // Optional settings (these are the defaults):
686
- // routes: "./src/routes.jsx", // Your routes file
686
+ // routes: "./src/routes.jsx", // Your routes file (and App.jsx for full layout)
687
687
  // outputDir: "dist",
688
688
  // generateSitemap: true,
689
689
  // useTrailingSlash: true, // Set to false for /about instead of /about/
@@ -836,6 +836,28 @@ In your netlify.toml:
836
836
 
837
837
  **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.
838
838
 
839
+ To include your full layout (headers, footers, etc.) in static generated HTML, export routes from the same file as your App component:
840
+
841
+ ```jsx
842
+ // App.jsx - export routes from here for full layout SSG
843
+ export const routes = [
844
+ { path: "/", element: <HomePage />, title: "Home" },
845
+ { path: "/about/", element: <AboutPage />, title: "About" },
846
+ ];
847
+
848
+ export default function App() {
849
+ return (
850
+ <main>
851
+ <Header />
852
+ <Routerino routes={routes} />
853
+ <Footer />
854
+ </main>
855
+ );
856
+ }
857
+ ```
858
+
859
+ This ensures your entire App layout is rendered during static site generation. Without this, SSG renders only the individual route elements.
860
+
839
861
  Define routes with an `element` property containing JSX elements:
840
862
 
841
863
  ```jsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routerino",
3
- "version": "2.2.1",
3
+ "version": "2.2.2-rc1",
4
4
  "description": "A lightweight, SEO-optimized React router for modern web applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -468,22 +468,103 @@ export function routerinoForge(options = {}) {
468
468
  import React from 'react';
469
469
  import ReactDOMServer from 'react-dom/server';
470
470
  import * as routesModule from '${relativePath.startsWith(".") ? relativePath : "./" + relativePath}';
471
+
471
472
  // Support different export patterns
472
473
  const routes = routesModule.routes || routesModule.default;
473
474
  const notFoundTemplate = routesModule.notFoundTemplate;
475
+
476
+ // Check if App component is exported from routes file
477
+ const App = routesModule.App || routesModule.default?.App;
478
+
474
479
  if (!routes) {
475
480
  throw new Error('Could not find routes export. Expected "export const routes" or "export default" from ${relativePath}');
476
481
  }
482
+
477
483
  // Helper to check if a route is dynamic (contains :param)
478
484
  const isDynamicRoute = (path) => path.split("/").some(segment => segment.startsWith(":"));
479
485
  export { routes };
480
- export function render(url) {
486
+
487
+ // Mock minimal window object for SSG
488
+ function mockWindow(url, baseUrl) {
489
+ const urlObj = new URL(url, baseUrl || 'http://localhost');
490
+ global.window = {
491
+ location: {
492
+ href: urlObj.href,
493
+ pathname: urlObj.pathname,
494
+ search: urlObj.search,
495
+ hash: urlObj.hash,
496
+ origin: urlObj.origin,
497
+ protocol: urlObj.protocol,
498
+ host: urlObj.host,
499
+ hostname: urlObj.hostname,
500
+ port: urlObj.port
501
+ },
502
+ history: {
503
+ pushState: () => {},
504
+ replaceState: () => {}
505
+ },
506
+ scrollTo: () => {},
507
+ addEventListener: () => {},
508
+ removeEventListener: () => {},
509
+ dispatchEvent: () => {}
510
+ };
511
+ global.document = {
512
+ addEventListener: () => {},
513
+ removeEventListener: () => {},
514
+ querySelector: () => null,
515
+ createElement: () => ({
516
+ setAttribute: () => {},
517
+ appendChild: () => {}
518
+ }),
519
+ head: {
520
+ appendChild: () => {}
521
+ }
522
+ };
523
+ }
524
+
525
+ export function render(url, baseUrl) {
526
+ // Check if we should render the full App or just the route element
527
+ if (App) {
528
+ // Mock window for the current route
529
+ mockWindow(url, baseUrl);
530
+
531
+ try {
532
+ // Render the full App component (which includes Routerino)
533
+ const html = ReactDOMServer.renderToString(React.createElement(App));
534
+
535
+ // Find the rendered route to get its metadata
536
+ const route = routes.find(r => {
537
+ if (r.path === url) return true;
538
+ if (r.path === '/' && url === '/') return true;
539
+ if (isDynamicRoute(r.path)) return false;
540
+ return r.path === url;
541
+ });
542
+
543
+ return {
544
+ html,
545
+ title: route?.title,
546
+ description: route?.description,
547
+ imageUrl: route?.imageUrl,
548
+ notFound: !route
549
+ };
550
+ } catch (error) {
551
+ console.error(\`[Routerino Forge] Failed to render App for route \${url}:\`, error.message);
552
+ // Fall back to route-only rendering
553
+ } finally {
554
+ // Clean up global mocks
555
+ delete global.window;
556
+ delete global.document;
557
+ }
558
+ }
559
+
560
+ // Original behavior: render just the route element
481
561
  const route = routes.find(r => {
482
562
  if (r.path === url) return true;
483
563
  if (r.path === '/' && url === '/') return true;
484
564
  if (isDynamicRoute(r.path)) return false;
485
565
  return r.path === url;
486
566
  });
567
+
487
568
  if (!route) {
488
569
  if (notFoundTemplate) {
489
570
  const notFoundHTML = ReactDOMServer.renderToString(notFoundTemplate);
@@ -491,6 +572,7 @@ export function render(url) {
491
572
  }
492
573
  return { html: '<div><h1>404 - Page Not Found</h1><p>The page you are looking for does not exist.</p></div>', notFound: true };
493
574
  }
575
+
494
576
  try {
495
577
  const html = ReactDOMServer.renderToString(route.element);
496
578
  return {
@@ -744,8 +826,8 @@ async function generateStaticPages({
744
826
  }
745
827
 
746
828
  try {
747
- // Use the render function to generate HTML
748
- const renderResult = render(route.path);
829
+ // Use the render function to generate HTML (pass baseUrl for window mocking)
830
+ const renderResult = render(route.path, config.baseUrl);
749
831
 
750
832
  let renderedHTML = "";
751
833
  if (renderResult.notFound) {
@@ -981,7 +1063,10 @@ async function generate404Page({ template, outputDir, config, render }) {
981
1063
 
982
1064
  try {
983
1065
  // Render a non-existent route to get the notFoundTemplate content
984
- const renderResult = render("/this-route-does-not-exist-404");
1066
+ const renderResult = render(
1067
+ "/this-route-does-not-exist-404",
1068
+ config.baseUrl
1069
+ );
985
1070
 
986
1071
  // The render function will return the notFoundTemplate HTML
987
1072
  const renderedHTML = renderResult.html || "404 - Page Not Found";