---
package: "@webjsdev/server"
version: 0.1.1
date: 2026-04-27T19:54:29+05:30
commit_count: 25
---
## Features

- **auto-vendor npm deps, module graph, lazy loading** [`266d041`](https://github.com/webjsdev/webjs/commit/266d041)
  Auto-vendor: scan client imports for bare specifiers, bundle via esbuild,
  serve at /__webjs/vendor/<pkg>.js, auto-populate import map.
  
  Module graph: build dependency graph at startup, emit transitive
- **abstract wire serializer, decouple from superjson** [`81ab2eb`](https://github.com/webjsdev/webjs/commit/81ab2eb)
  New Serializer interface with getSerializer/setSerializer. superjson
  is the default but can be swapped via webjs.config.js. Decouples the
  RPC layer from a single dependency.
- **optional catch-all, nested not-found, loading→Suspense, metadata routes** [`332f41f`](https://github.com/webjsdev/webjs/commit/332f41f)
  - [[...slug]] optional catch-all matches with and without params
  - not-found.ts collected at every segment level (nearest wins)
  - loading.ts auto-wraps page in Suspense boundary with loading as fallback
  - Metadata routes: sitemap.ts, robots.ts, manifest.ts, icon.ts,
- **webjs test, webjs check, webjs create with AI guardrails** [`6e85e5d`](https://github.com/webjsdev/webjs/commit/6e85e5d)
  - webjs test: discovers and runs unit + E2E tests
  - webjs check: validates app against 6 convention rules
  - webjs create: scaffolds opinionated project with CONVENTIONS.md,
    cross-agent configs (.cursorrules, .windsurfrules, copilot-instructions),
- **batteries included — sessions, jobs, pub/sub, storage, cache** [`6292558`](https://github.com/webjsdev/webjs/commit/6292558)
  Convention over configuration, like Rails:
  - Set REDIS_URL → cache, sessions, rate limiter, pub/sub, and jobs
    all use Redis automatically. Zero config.
  - Set S3_BUCKET → file storage uses S3. Otherwise local disk.
- **NextJs-style cache + NextAuth-style auth + WebSocket broadcast** [`19418b3`](https://github.com/webjsdev/webjs/commit/19418b3)
  Removed: storage (s3/disk), background jobs — not core framework.
  
  Added:
  - cache(fn, { revalidate, tags }) + revalidateTag() — NextJs-style
- **HTTP Cache-Control on SSR pages via metadata export** [`74edab3`](https://github.com/webjsdev/webjs/commit/74edab3)
  Pages can now set Cache-Control headers via the metadata export:
  
    export const metadata = {
      title: 'Blog',
- **cache() function for server-side RPC query caching** [`504a982`](https://github.com/webjsdev/webjs/commit/504a982)
  Simple, explicit, no magic:
  
    export const listPosts = cache(
      async () => prisma.post.findMany(),
- **auto-wrap layout output with data-layout for client router** [`306c512`](https://github.com/webjsdev/webjs/commit/306c512)
  The SSR pipeline now automatically wraps the outermost layout's
  rendered output in <div data-layout="LAYOUT_ID">. Users no longer
  need to add data-layout manually — the framework handles it.
- **SSR pages default to no-store, caching is opt-in** [`5a4468c`](https://github.com/webjsdev/webjs/commit/5a4468c)
  SSR responses now send Cache-Control: no-store by default. Pages
  are dynamic — the developer opts in to caching explicitly via
  metadata.cacheControl (e.g. 'public, max-age=60'). This ensures
  client-side navigation always gets fresh content without needing
- **Class.register() — drop the import.meta.url argument** [`283c2b5`](https://github.com/webjsdev/webjs/commit/283c2b5)
  Authors no longer write `Counter.register(import.meta.url)`; the call
  is just `Counter.register()`. Module URLs (used by SSR to emit
  `<link rel=modulepreload>` hints) are derived server-side at boot by
  scanning the app tree for `class X extends WebComponent { static tag =
- **adopt standard customElements.define — drop static tag + .register()** [`bb61186`](https://github.com/webjsdev/webjs/commit/bb61186)
  The web-standard registration convention (same shape Lit uses for its
  non-decorator form):
  
    class Counter extends WebComponent { ... }
- **Counter.register('my-counter') as the idiomatic registration** [`a0f1f68`](https://github.com/webjsdev/webjs/commit/a0f1f68)
  Adds a `static register(tag)` method on WebComponent. Delegates to the
  internal registry (which in turn calls customElements.define / the
  server shim), so every registration still lands in the native
  customElements map and webjs's mirror map. Authors write:
- **rename core package webjs → @webjs/core for npm publish** [`49e2d6b`](https://github.com/webjsdev/webjs/commit/49e2d6b)
  The unscoped `webjs` name is already taken on npm (webjs@0.6.1, unrelated
  project). Moving to the `@webjs` scope lets us publish `@webjs/core`,
  `@webjs/server`, and `@webjs/cli` together.
- **rescope @webjs → @webjskit (org name 'webjs' unavailable on npm)** [`316e2be`](https://github.com/webjsdev/webjs/commit/316e2be)
  The @webjs organization is not available on npm (likely reserved because
  the unscoped `webjs` package is held by an unrelated project). Switching
  to the @webjskit scope, which we own.
- **social-preview cards for website, docs, and blog** [`b16fc68`](https://github.com/webjsdev/webjs/commit/b16fc68)
  Adds proper Open Graph + Twitter summary_large_image metadata so the
  three apps preview cleanly when shared on WhatsApp, Twitter/X, LinkedIn,
  Slack, Discord, etc.

## Fixes

- **decode percent-encoded URL paths before filesystem lookups** [`72caa14`](https://github.com/webjsdev/webjs/commit/72caa14)
  Root cause of ALL client-side interactivity failing in production:
  
  The SSR shell emits module imports with literal filesystem paths:
    import "/app/blog/[slug]/page.ts"
- **bundle superjson into a single ESM file for the browser** [`a13cbea`](https://github.com/webjsdev/webjs/commit/a13cbea)
  Root cause of ALL client-side JS failing:
  
  superjson internally imports \`copy-anything\` which imports \`is-what\` —
  both as bare specifiers. The browser's import map only had an entry for
- **production hardening — cache eviction + CSP nonce support** [`c559de2`](https://github.com/webjsdev/webjs/commit/c559de2)
  Cache eviction: TS transform cache capped at 500 entries, vendor
  bundle cache at 100. FIFO eviction prevents unbounded memory growth
  in long-running servers.
- **remove all auto-detection magic from session and cache** [`3a52624`](https://github.com/webjsdev/webjs/commit/3a52624)
  Cleaned up REDIS_URL auto-detection references from session.js and
  cache.js JSDoc. Session defaults to cookie (explicit, stateless).
  Cache defaults to memory (explicit). User switches to Redis by
  calling setStore/passing storeSession — no env var magic.
- **export Session class from @webjs/server** [`eae64cb`](https://github.com/webjsdev/webjs/commit/eae64cb)
- **hoist scripts/styles to head + data-layout for router detection** [`7bcebf0`](https://github.com/webjsdev/webjs/commit/7bcebf0)
  Two fixes for client-side navigation with light DOM layouts:
  
  1. SSR hoistHeadTags: leading <script> and <style> tags from the body
     are moved to <head>. Tailwind browser script, @theme styles, and
- **exclude server-only modules from <link rel=modulepreload>** [`36ea61b`](https://github.com/webjsdev/webjs/commit/36ea61b)
  Server-only files (either .server.{js,ts,mjs,mts} or plain .ts with a
  'use server' directive) were appearing in modulepreload links because
  the transitive-deps walk treats them the same as any other import.
  Browsers fetched them on every page load — the response body is a
- **hard guardrail — never serve server-file source to the client** [`a4df002`](https://github.com/webjsdev/webjs/commit/a4df002)
  The dev HTTP layer used to look up .server.* / 'use server' files via
  the action index built at boot; on index miss it fell through to
  tsResponse() and returned the RAW SOURCE. That's a serious leak
  vector: DB credentials, scrypt routines, privileged business logic
- **resolve esbuild from app dir, not server dir** [`4987a04`](https://github.com/webjsdev/webjs/commit/4987a04)
  When `@webjskit/cli` is installed globally (the typical case for
  end-users), `import('esbuild')` from `packages/server/src/dev.js`
  walks up from the global install tree — never reaching the user app's
  `node_modules`. The dev server then served `.ts` files as
