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

vite-runtime-config-kit

v0.1.0

Published

Runtime configuration for Vite apps - one build, many environments. Fetch, validate (Standard Schema) and consume a runtime-config.json at container start, with a React provider/hook. Zero runtime dependencies.

Readme

vite-runtime-config-kit

npm

Runtime configuration for Vite apps: one build, many environments. Fetch, validate and consume a runtime-config.json at container start, with a small React provider/hook on top. Zero runtime dependencies.

  • Framework-agnostic core (loadRuntimeConfig) with no window/document usage, so it works in SSR and Node tests.
  • Optional React entry (vite-runtime-config-kit/react) with a typed provider/hook.
  • Validation through Standard Schema v1 (zod >= 3.24, valibot, arktype, ...) or a plain validate function - no validation library is a dependency of this package.
  • Timeout, retries with backoff, and a single typed error (RuntimeConfigError).

The problem

Vite inlines import.meta.env.* at build time. A single static image therefore cannot easily receive different values when the container starts, which breaks the "build once, deploy many" model used with Docker, Kubernetes, Cloud Run, and similar. This is a long-standing, recurring request:

  • https://github.com/vitejs/vite/issues/10059
  • https://github.com/vitejs/vite/issues/16069
  • https://github.com/vitejs/vite/issues/17848
  • https://github.com/vitejs/vite/issues/8021
  • https://github.com/vitejs/vite/discussions/6387
  • https://github.com/vitejs/vite/discussions/3855
  • https://github.com/vitejs/vite/discussions/19200

The robust workaround is to serve a small JSON (or JS) file that is generated at container start and fetched by the app before it renders. This package implements the fetch + validate + consume side of that pattern.

Install

npm install vite-runtime-config-kit

react is an optional peer dependency, only needed if you import vite-runtime-config-kit/react. The core has no dependencies at all.

Quick start

The config must be loaded before React renders. Do it before createRoot so the whole tree can rely on it being present - there is intentionally no fetching inside the provider.

// main.tsx
import { createRoot } from "react-dom/client";
import { z } from "zod";
import { loadRuntimeConfig } from "vite-runtime-config-kit";
import { RuntimeConfigProvider } from "vite-runtime-config-kit/react";
import { App } from "./App";

const schema = z.object({
  API_URL: z.string().url(),
  ENVIRONMENT: z.enum(["dev", "test", "prod"]),
});

const config = await loadRuntimeConfig({
  source: "/runtime-config.json",
  schema,
});

createRoot(document.getElementById("root")!).render(
  <RuntimeConfigProvider config={config}>
    <App />
  </RuntimeConfigProvider>,
);
// somewhere in a component
import { useRuntimeConfig } from "vite-runtime-config-kit/react";

function ApiStatus() {
  const { API_URL } = useRuntimeConfig<AppConfig>();
  return <span>{API_URL}</span>;
}

Typed pair without repeating the generic

createRuntimeConfig<T>() returns a Provider / useConfig pair already bound to your config type, so consumers do not pass a generic every time:

// config.ts
import { createRuntimeConfig } from "vite-runtime-config-kit/react";

export interface AppConfig {
  API_URL: string;
  ENVIRONMENT: "dev" | "test" | "prod";
}

export const { Provider, useConfig } = createRuntimeConfig<AppConfig>();
const { API_URL } = useConfig(); // typed as AppConfig, no generic needed

API

loadRuntimeConfig<T>(options?): Promise<T>

| Option | Default | Description | | -------------- | ------------------------ | --------------------------------------------------------------------------- | | source | "/runtime-config.json" | URL to fetch the JSON from. | | schema | - | A Standard Schema v1 schema (zod/valibot/arktype). T is inferred from it. | | validate | - | A plain (data) => T function (used when no schema). Throw to reject. | | fetchFn | globalThis.fetch | Custom fetch (tests, non-global fetch). | | timeoutMs | 10000 | Abort the request after this many ms (AbortController). | | retries | 2 | Retries after a retryable failure (see below). | | retryDelayMs | 500 | Delay between retries. | | cache | "no-store" | Forwarded to fetch - avoids stale CDN/browser copies. |

Retries apply only to transient failures: network errors, timeouts, and HTTP 5xx. A 4xx response, a JSON parse error, and a validation failure are not retried. Validation runs once, after the fetch/retry loop succeeds.

RuntimeConfigError

Every failure is thrown as a single error class with a kind discriminator:

| kind | Meaning | Extra fields | | ------------ | ------------------------------------------------ | ------------- | | network | fetch rejected (offline, DNS, CORS). | cause | | timeout | Aborted after timeoutMs. | cause | | http | Non-2xx response. | status | | parse | Body was not valid JSON. | cause | | validation | JSON fetched, but failed the schema/validate fn. | issues |

All instances also carry url. This makes a readable error screen easy:

try {
  const config = await loadRuntimeConfig({ schema });
  // ...
} catch (e) {
  if (e instanceof RuntimeConfigError) {
    // e.kind === "validation" -> show e.issues; otherwise "config unavailable".
  }
}

React entry (vite-runtime-config-kit/react)

  • RuntimeConfigProvider({ config, children }) - provides an already-loaded config. No fetching.
  • useRuntimeConfig<T>() - reads the config; throws a readable error when used outside a provider.
  • createRuntimeConfig<T>() - returns a typed { Provider, useConfig } pair with its own React context.

Why not env replacement at container start

A tempting alternative is to sed-replace placeholder strings inside the built JavaScript bundle at container start. Avoid this - it is brittle: placeholders can be renamed or split by minification, source maps drift, and a single missed token silently ships the wrong value. The stable model is to keep the bundle immutable and serve configuration as a separate file the app fetches:

<script src="/runtime-config.js"></script>

or

await fetch("/runtime-config.json");

This package uses the fetch variant.

Docker / nginx example

Generate runtime-config.json from environment variables in the container entrypoint, then start the server. (A dedicated CLI to generate this file with schema validation is planned for 0.2 - the snippet below is plain sh.)

#!/bin/sh
# docker-entrypoint.sh
set -eu

cat > /usr/share/nginx/html/runtime-config.json <<EOF
{
  "API_URL": "${API_URL}",
  "ENVIRONMENT": "${ENVIRONMENT}"
}
EOF

exec nginx -g "daemon off;"
# Dockerfile (excerpt)
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]

Serve runtime-config.json with Cache-Control: no-store (and the app fetches it with cache: "no-store" by default) so a new deployment is picked up immediately rather than served stale from a CDN or the browser cache.

Limitations

  • Runtime config is public: it ships to the browser. Never put secrets (API keys, passwords, private keys) in it.
  • The config must be loaded before React renders (bootstrap before createRoot); the provider does not fetch.
  • Stale caches are a real risk - keep no-store on both the response and the fetch unless you add explicit cache busting.
  • 0.1 covers the runtime side only. A config-generating CLI (0.2) and a Vite plugin (0.3) are planned.

License

MIT