npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@medicomind/svelte-adapter-hono

v1.0.1

Published

SvelteKit adapter that builds a standalone Node server powered by Hono, with brotli/gzip/zstd precompression

Readme

@medicomind/svelte-adapter-hono

CI Release npm version npm downloads node license

SvelteKit adapter that builds a standalone Node server powered by Hono (hono + @hono/node-server) — a drop-in alternative to @sveltejs/adapter-node with built-in brotli, gzip and zstd precompression served via Accept-Encoding negotiation.

  • Self-contained build/ output: node build and you're serving.
  • Static assets → prerendered pages → SSR, in that order, with zero sync fs calls and no per-request buffering on the hot path (file existence is resolved from a manifest built once at boot).
  • .gz / .br / .zst sidecars generated at build time and negotiated per request with full q-value parsing. Compression runs on native Rust (@medicomind/rolldown-compression) — about 2× faster than node:zlib on our benchmarks.
  • Composable: embed the generated Hono app inside your own server.

Installation

npm install --save-dev @medicomind/svelte-adapter-hono hono @hono/node-server

@sveltejs/kit, hono and @hono/node-server are peer dependencies — the emitted build bundles the Hono version installed in your project.

Usage

// svelte.config.js
import adapter from '@medicomind/svelte-adapter-hono';

/** @type {import('@sveltejs/kit').Config} */
export default {
	kit: {
		adapter: adapter({
			out: 'build',
			precompress: true,
			envPrefix: '',
			runtimeConfig: { bodySizeLimit: '1M' }
		})
	}
};

Build and run:

npm run build
node build

Options

| Option | Type | Default | Description | | --------------- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------- | | out | string | 'build' | Output directory for the standalone build. | | precompress | boolean \| PrecompressOptions | true | Generate .gz/.br/.zst sidecars for static assets and prerendered pages (see below). | | envPrefix | string | '' | Prefix for all runtime environment variables (e.g. 'MY_APP_'MY_APP_PORT, MY_APP_HOST …). | | runtimeConfig | RuntimeConfig | {} | Typed runtime configuration fixed at build time; wins over environment variables (see below). |

precompress

true enables all three encodings. An object gives fine-grained control:

adapter({
	precompress: {
		gzip: true, // .gz  via gzip, level 9      (default true)
		brotli: true, // .br  via brotli, quality 11 (default true)
		zstd: true, // .zst via zstd, level 19     (default true)
		files: ['html', 'js', 'json', 'css', 'svg', 'xml', 'wasm', 'txt', 'map', 'ico', 'webmanifest']
	}
});
  • Only files ≥ 1024 bytes whose extension is in the files allowlist are compressed.
  • Compression is powered by @medicomind/rolldown-compression, a native (Rust, napi-rs) rolldown plugin that compresses files in parallel across all logical CPUs. We use it instead of node:zlib because it compressed build output about 2× faster on our benchmarks — at the maximum compression levels above, precompression dominates build time for asset-heavy apps.

runtimeConfig

Fixes runtime configuration at build time, with full type checking. Every field maps to one of the runtime environment variables; the server reads its configuration from the environment by default, and any field set here is baked into the build and takes precedence over the corresponding environment variable at runtime:

adapter({
	runtimeConfig: {
		port: 8080, // overrides PORT
		bodySizeLimit: '10M', // overrides BODY_SIZE_LIMIT
		addressHeader: 'x-forwarded-for' // overrides ADDRESS_HEADER
	}
});

The RuntimeConfig type (exported from the package) documents every field:

interface RuntimeConfig {
	port?: number; // PORT              — integer 0–65535; 0 → random ephemeral port
	host?: string; // HOST              — interface to bind
	socketPath?: string; // SOCKET_PATH       — unix socket; wins over port/host
	origin?: string; // ORIGIN            — public origin, e.g. 'https://example.com'
	protocolHeader?: string; // PROTOCOL_HEADER   — e.g. 'x-forwarded-proto'
	hostHeader?: string; // HOST_HEADER       — e.g. 'x-forwarded-host'
	portHeader?: string; // PORT_HEADER       — e.g. 'x-forwarded-port'
	addressHeader?: string; // ADDRESS_HEADER    — e.g. 'x-forwarded-for'
	xffDepth?: number; // XFF_DEPTH         — positive integer
	bodySizeLimit?: number | string; // BODY_SIZE_LIMIT   — bytes, 'K'/'M'/'G' suffix or Infinity
	compressOnDemand?: boolean; // COMPRESS_ON_DEMAND — on-the-fly compression of dynamic responses
	shutdownTimeout?: number; // SHUTDOWN_TIMEOUT  — seconds ≥ 0
	idleTimeout?: number; // IDLE_TIMEOUT      — seconds ≥ 0
}

Values are validated when svelte.config.js is loaded — an unknown field or a value of the wrong shape (e.g. port: 1.5, bodySizeLimit: '10KB') fails the build immediately. envPrefix does not apply to runtimeConfig (it only prefixes environment variables).

Serving negotiation

For static assets and prerendered pages the server parses Accept-Encoding with q-values and picks the best available sidecar. When q-values tie, preference is zstd > br > gzip > identity. Responses carry the correct content-encoding, the original content-type and vary: accept-encoding. Range requests are never served from compressed sidecars. When no sidecar matches, the identity file is streamed.

Compress on demand

Precompression only covers files that exist at build time. With runtimeConfig: { compressOnDemand: true } (or COMPRESS_ON_DEMAND=true at runtime) the server additionally compresses dynamic responses — SSR pages, endpoints, and any static file without a matching sidecar — on the fly with node:zlib, using the same Accept-Encoding negotiation and zstd > br > gzip tie-breaking. It's off by default.

  • Bodies are streamed through the encoder, never buffered; content-length is dropped and vary: accept-encoding added.
  • Levels are tuned for latency (gzip 6, brotli 4, zstd 3) — unlike the build-time precompression, which maxes them out.
  • Skipped for responses that already have a content-encoding (e.g. precompressed sidecars), carry cache-control: no-transform, declare a content-length under 1024 bytes, have status 206/304, or whose content-type isn't compressible (text/* except text/event-stream, +json/+xml syntaxes, JSON/JS/XML/SVG/WASM and a few font types).

If a reverse proxy or CDN in front of the server already compresses responses, leave this off.

Runtime environment variables

All names respect envPrefix. Each variable can also be fixed at build time via the typed runtimeConfig adapter option (right column), in which case the baked-in value wins over the environment.

| Variable | Default | Description | runtimeConfig field | | ------------------ | --------- | ---------------------------------------------------------------------------------------------------------------- | --------------------- | | PORT | 3000 | Port to listen on (0 → random ephemeral port). | port | | HOST | 0.0.0.0 | Interface to bind. | host | | SOCKET_PATH | — | Unix domain socket path; overrides PORT/HOST when set. | socketPath | | ORIGIN | — | Public origin of the app (https://example.com). Wins over the header options below. | origin | | PROTOCOL_HEADER | — | Header carrying the original protocol behind a proxy (e.g. x-forwarded-proto). | protocolHeader | | HOST_HEADER | — | Header carrying the original host (e.g. x-forwarded-host). | hostHeader | | PORT_HEADER | — | Header carrying the original port (e.g. x-forwarded-port). | portHeader | | ADDRESS_HEADER | — | Header to read the client IP from (e.g. x-forwarded-for). | addressHeader | | XFF_DEPTH | 1 | With ADDRESS_HEADER=x-forwarded-for: how many proxies deep to look, counting from the right. | xffDepth | | BODY_SIZE_LIMIT | 512K | Max request body size in bytes; supports K/M/G suffixes and Infinity (0 also disables). 413 on exceed. | bodySizeLimit | | SHUTDOWN_TIMEOUT | 30 | Seconds to wait for in-flight requests after SIGINT/SIGTERM before force-closing sockets. | shutdownTimeout | | IDLE_TIMEOUT | 0 | If > 0: gracefully shut down after this many seconds without in-flight requests. 0 disables. | idleTimeout |

Graceful shutdown

On SIGINT/SIGTERM the server stops accepting connections, lets in-flight requests finish (up to SHUTDOWN_TIMEOUT seconds), then force-closes lingering sockets. Once fully closed, a sveltekit:shutdown event is emitted on process with the reason (SIGINT, SIGTERM or IDLE):

process.on('sveltekit:shutdown', async (reason) => {
	await db.close();
	console.log(`shut down: ${reason}`);
});

Custom server / embedding

The build emits composable modules alongside index.js:

// A Hono instance — mount it in your own Hono server
import { Hono } from 'hono';
import { app } from './build/app.js';

const server = new Hono();
server.get('/healthz', (c) => c.text('ok'));
server.route('/', app); // static → prerendered → SSR
// A fetch-style handler: (request: Request) => Promise<Response>
import { handler } from './build/handler.js';

const response = await handler(new Request('http://localhost/'));

env.js (prefixed env reader) and shims.js (SvelteKit's Node polyfills, side-effect import) are also emitted. When embedding, the client IP falls back to the @hono/node-server socket when available; behind another runtime configure ADDRESS_HEADER.

Comparison with @sveltejs/adapter-node

| Feature | adapter-node | @medicomind/svelte-adapter-hono | | ---------------------------------------------- | ----------------------- | -------------------------------------------- | | HTTP layer | polka + sirv | hono + @hono/node-server | | Precompression | gzip, brotli | gzip, brotli, zstd | | Precompression engine | node:zlib | native Rust (~2× faster, parallel) | | Precompressed serving with q-value negotiation | ✅ | ✅ (+ zstd > br > gzip tie-breaking) | | ORIGIN / proxy header handling | ✅ | ✅ (same env vars) | | BODY_SIZE_LIMIT | ✅ | ✅ (same syntax, streaming enforcement) | | Graceful shutdown + sveltekit:shutdown | ✅ | ✅ (+ standalone IDLE_TIMEOUT) | | Embeddable handler | Node req/res middleware | fetch-style handler + mountable Hono app | | systemd socket activation | ✅ | ❌ out of scope (see below) | | HTTP/2, TLS termination, clustering | ❌ | ❌ out of scope |

Out of scope

systemd socket activation (LISTEN_FDS), HTTP/2, TLS termination, clustering, and on-the-fly compression of dynamic SSR responses are intentionally not implemented (put a reverse proxy or CDN in front, or open an issue if you need them). IDLE_TIMEOUT here is a standalone idle shutdown rather than a socket-activation companion.

Troubleshooting

Form actions fail with cross-site POST errors — SvelteKit can't infer the public origin behind a proxy. Set ORIGIN=https://your.site, or PROTOCOL_HEADER=x-forwarded-proto + HOST_HEADER=x-forwarded-host (+ PORT_HEADER if a non-default port) if your proxy sets them.

event.getClientAddress() returns the proxy IP — set ADDRESS_HEADER=x-forwarded-for and make sure XFF_DEPTH matches the number of trusted proxies in front of the server (it counts from the right of the header).

Only ADDRESS_HEADER you trust — any client can send x-forwarded-for; only configure headers your proxy overwrites.

413 Payload Too Large on legitimate uploads — raise BODY_SIZE_LIMIT (e.g. BODY_SIZE_LIMIT=10M), or set it to Infinity and enforce limits in your own handlers.

Unknown prefixed env vars warning — with envPrefix set, unrecognized PREFIX_* variables log a warning to catch typos; unprefixed variables are ignored entirely.

Output layout

build/
├── index.js        # server entry: node build
├── handler.js      # exports { app, handler }
├── app.js          # re-exports { app, handler } for embedding
├── env.js          # prefixed env reader
├── shims.js        # SvelteKit Node polyfills
├── client/         # static assets (+ .gz/.br/.zst sidecars)
├── prerendered/    # prerendered pages (+ sidecars)
└── server/         # bundled SvelteKit server + manifest

License

MIT