Skip to content

Latest commit

 

History

History
193 lines (138 loc) · 5.62 KB

File metadata and controls

193 lines (138 loc) · 5.62 KB

Service worker setup

Nodepod routes preview iframes (nodepod.port(3000)) and virtual HTTP servers through a service worker. The SW intercepts same-origin fetches and hands them off to Nodepod's in-browser Node runtime.

Unlike the rest of the package, the SW cannot be bundled into your app JS. Browsers impose two hard requirements:

  1. A service worker must be served from the same origin as the page that registers it.
  2. It must be served with a JavaScript Content-Type (e.g. application/javascript). Serving it as text/html, which is what SPA dev servers fall back to for unknown routes, silently breaks registration.

So: a file has to sit on your host at /__sw__.js, answering with a JS Content-Type. This doc shows how to make that happen in every common framework, and what error you'll see if you don't.

The one-liners

Vite

// vite.config.ts
import { defineConfig } from 'vite';
import nodepod from '@scelar/nodepod/vite';

export default defineConfig({
  plugins: [nodepod()],
});
  • Dev: the plugin adds middleware that serves /__sw__.js from the in-memory source.
  • Build: the plugin emits __sw__.js as an asset next to your other build output, so your production host serves it automatically.

Optional: nodepod({ path: '/foo/__sw__.js' }) to mount under a custom path. Pair with Nodepod.boot({ swUrl: '/foo/__sw__.js' }).

Next.js (App Router)

Works the same across Next 13 through 16. Route handlers weren't touched by the Next 16 rename.

// app/__sw__.js/route.ts
export { GET } from '@scelar/nodepod/next';

Next matches this file at GET /__sw__.js because the folder name is literally __sw__.js. No other config required.

Next 16+ (proxy.ts)

Next 16 renamed middleware.ts to proxy.ts. If you already have a proxy.ts, compose nodepodProxy:

// proxy.ts
import { nodepodProxy, nodepodMatcher } from '@scelar/nodepod/next';

export async function proxy(req) {
  const sw = await nodepodProxy(req);
  if (sw) return sw;
  // ...your own proxy logic
}

export const config = { matcher: [nodepodMatcher /*, your paths */] };

Next <=15 (middleware.ts)

nodepodMiddleware is a back-compat alias of nodepodProxy, for projects still on middleware.ts:

// middleware.ts
import { nodepodMiddleware, nodepodMatcher } from '@scelar/nodepod/next';

export async function middleware(req) {
  const sw = await nodepodMiddleware(req);
  if (sw) return sw;
  // ...your own middleware
}

export const config = { matcher: [nodepodMatcher /*, your paths */] };

Upgrading to Next 16? Run npx @next/codemod@canary middleware-to-proxy . to rename your file + function, then swap nodepodMiddleware for nodepodProxy. They're the same function re-exported under both names.

Any Fetch-style framework (Hono, Bun, Cloudflare, Elysia, etc.)

import { serveSW } from '@scelar/nodepod/server';

app.get('/__sw__.js', () => serveSW());

Express / Fastify / bare http

import { serveSWNode } from '@scelar/nodepod/server';

app.get('/__sw__.js', async (_req, res) => {
  const { body, headers } = await serveSWNode();
  for (const [k, v] of Object.entries(headers)) res.setHeader(k, v);
  res.status(200).send(body);
});

Static host (copy once)

No server to edit? Copy the file into your public/static directory and let the host serve it:

cp node_modules/@scelar/nodepod/dist/__sw__.js public/__sw__.js

Re-run the copy when you upgrade @scelar/nodepod.

What the error looks like

Starting in 1.2, Nodepod.boot() registers the service worker by default. If the SW can't be reached or is served as HTML, boot throws:

NodepodSWSetupError: service worker at /__sw__.js returned HTTP 404

  Requested:    /__sw__.js
  HTTP status:  404

Detected Vite. Add the nodepod plugin to serve __sw__.js automatically:

  // vite.config.ts
  import nodepod from '@scelar/nodepod/vite';
  export default defineConfig({ plugins: [nodepod()] });

The framework hint is picked automatically by sniffing the runtime (import.meta.hot, window.__NEXT_DATA__, etc.).

Opting out

Two knobs:

await Nodepod.boot({
  // Skip SW registration entirely (SSR, Node tests, hosts without a SW).
  serviceWorker: false,

  // Or: keep SW on, but skip the HEAD preflight (use if your host
  // disallows HEAD, requires auth, or trips the check some other way).
  skipSWPreflight: true,
});

With serviceWorker: false you keep the rest of Nodepod (filesystem, spawn, packages) but preview iframes and nodepod.request() to virtual ports won't work.

Customising the URL

If you can't use /__sw__.js (maybe it's already taken), serve the SW somewhere else and tell the SDK:

await Nodepod.boot({ swUrl: '/assets/nodepod-sw.js' });

Framework integrations support this too:

// Vite
nodepod({ path: '/assets/nodepod-sw.js' });

// Fetch-style
app.get('/assets/nodepod-sw.js', () => serveSW());

Troubleshooting

Symptom Likely cause Fix
NodepodSWSetupError: HTTP 404 No handler mounted Add the plugin / route for your framework above
NodepodSWSetupError: wrong Content-Type (text/html) SPA fallback catching /__sw__.js Ensure your router serves the SW before the fallback
NodepodSWSetupError: could not be reached CORS, network error, or timeout Check devtools → Network; make sure the URL is same-origin
SW registers but preview iframes are blank Nodepod's Cross-Origin-* headers missing Set Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: credentialless on HTML responses