nukejs 0.0.31 → 0.0.32

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
@@ -28,6 +28,7 @@ npm create nuke@latest
28
28
  - [useRequest() — URL Params, Query & Headers](#userequest--url-params-query--headers)
29
29
  - [cache() — Request-Scoped Data Caching](#cache--request-scoped-data-caching)
30
30
  - [Error Pages](#error-pages)
31
+ - [renderComponent() — SSR Outside the Page Router](#rendercomponent--ssr-outside-the-page-router)
31
32
  - [Building & Deploying](#building--deploying)
32
33
 
33
34
  ## Overview
@@ -1378,6 +1379,62 @@ The `_500.tsx` page receives `errorMessage` and `errorStack` props from client e
1378
1379
 
1379
1380
  ---
1380
1381
 
1382
+ ## renderComponent() — SSR Outside the Page Router
1383
+
1384
+ `renderComponent()` renders any React component to a full HTML document with the same SSR pipeline pages use — but without requiring a file in `app/pages/`. Use it for DB-driven routes, CMS content, webhook-rendered HTML, server-rendered emails, or any endpoint that needs real React SSR.
1385
+
1386
+ ```ts
1387
+ import { renderComponent } from 'nukejs/server';
1388
+ import Widget from './components/Widget';
1389
+ import RootLayout from './app/pages/layout';
1390
+
1391
+ export async function GET(req, res) {
1392
+ const html = await renderComponent(Widget, { title: 'Hello' }, {
1393
+ layouts: [RootLayout],
1394
+ url: req.url,
1395
+ title: 'My Widget Page',
1396
+ });
1397
+ res.setHeader('Content-Type', 'text/html');
1398
+ res.end(html);
1399
+ }
1400
+ ```
1401
+
1402
+ ### API
1403
+
1404
+ ```ts
1405
+ renderComponent(
1406
+ Component: ComponentType<any>,
1407
+ props?: Record<string, unknown>,
1408
+ options?: RenderComponentOptions,
1409
+ ): Promise<string>
1410
+ ```
1411
+
1412
+ | Option | Type | Default | Description |
1413
+ |---|---|---|---|
1414
+ | `layouts` | `ComponentType<{ children }>[]` | `[]` | Layout components to wrap the element in, outermost last |
1415
+ | `url` | `string` | `'/'` | Exposed via `useRequest()` inside the component |
1416
+ | `params` | `Record<string, string \| string[]>` | — | Route params exposed via `useRequest()` |
1417
+ | `query` | `Record<string, string \| string[]>` | — | Query params exposed via `useRequest()` |
1418
+ | `headers` | `Record<string, string>` | — | Request headers exposed via `useRequest()` |
1419
+ | `isDev` | `boolean` | auto-detected | Whether to enable dev-mode behaviour |
1420
+ | `title` | `string` | `'NukeJS'` | Fallback `<title>` if the component never calls `useHtml({ title })` |
1421
+
1422
+ ### Import path
1423
+
1424
+ `renderComponent` is exported from `nukejs/server`, not from `nukejs`. This separation exists because the main `nukejs` entry is also resolved by the client-component bundler (browser platform) — server-only code with Node built-in dependencies must live under a separate subpath.
1425
+
1426
+ ### Hydration
1427
+
1428
+ `renderComponent()` produces pure server-rendered markup with no `"use client"` hydration wiring. This is intentional — detecting hydration boundaries requires reading source files off disk, which isn't available on Cloudflare Workers and isn't reliable in pre-bundled Vercel functions.
1429
+
1430
+ For interactive islands inside a `renderComponent()` result, mount them as normal client-side widgets the browser initialises separately, rather than relying on NukeJS's SSR hydration markers.
1431
+
1432
+ ### Platform support
1433
+
1434
+ Because `renderComponent()` takes an already-imported component (a normal static ES import in your code), your own bundler resolves it at build time. It has no filesystem dependencies and works identically on Node.js, Vercel, and Cloudflare Workers.
1435
+
1436
+ ---
1437
+
1381
1438
  ## Building & Deploying
1382
1439
 
1383
1440
  ### Node.js server
package/dist/bundle.js CHANGED
@@ -2,15 +2,25 @@ function setupLocationChangeMonitor() {
2
2
  const originalPushState = window.history.pushState.bind(window.history);
3
3
  const originalReplaceState = window.history.replaceState.bind(window.history);
4
4
  const dispatch = (href) => window.dispatchEvent(new CustomEvent("locationchange", { detail: { href } }));
5
+ let lastPath = window.location.pathname + window.location.search;
5
6
  window.history.pushState = function(...args) {
6
7
  originalPushState(...args);
8
+ lastPath = window.location.pathname + window.location.search;
7
9
  dispatch(args[2]);
8
10
  };
9
11
  window.history.replaceState = function(...args) {
10
12
  originalReplaceState(...args);
13
+ lastPath = window.location.pathname + window.location.search;
11
14
  dispatch(args[2]);
12
15
  };
13
- window.addEventListener("popstate", () => dispatch(window.location.pathname + window.location.search));
16
+ window.addEventListener("popstate", () => {
17
+ const currentPath = window.location.pathname + window.location.search;
18
+ if (currentPath === lastPath) {
19
+ return;
20
+ }
21
+ lastPath = currentPath;
22
+ dispatch(currentPath);
23
+ });
14
24
  }
15
25
  function makeLogger(level) {
16
26
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nukejs",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "description": "A minimal, opinionated full-stack React framework on Node.js that server-renders everything and hydrates only interactive parts.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",