clickman6 / laravel-vite-env
Serve whitelisted VITE_* environment values as /vite-env.js, sourced from Laravel's cached config — no files, no .env at runtime.
Requires
- php: ^8.3
- illuminate/support: ^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- orchestra/testbench: ^11.0
- phpstan/extension-installer: ^1.4
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-07-24 21:53:21 UTC
README
Serves a public /vite-env.js with VITE_* environment values, sourced from Laravel's config()
(not directly from .env). No file generation, works with php artisan config:cache, and fits
immutable Docker images.
Installation
composer require clickman6/laravel-vite-env
The provider is registered automatically via Laravel package discovery.
Quick start
In .env:
VITE_APP_NAME=MyApp
VITE_API_URL=https://api.example.com
Publish the JS helper:
php artisan vendor:publish --tag=vite-env-js
In your Blade template — @viteEnv inserts <script src="/vite-env.js?v=..."> with the current
config version, before the main bundle:
@viteEnv <script type="module" src="/resources/js/app.js"></script>
In frontend code, read values through env(), not directly via window.__ENV__ — it also falls
back to import.meta.env.VITE_* for local development without a backend:
import { env } from './vendor/laravel-vite-env/env.js'; console.log(env('VITE_APP_NAME')); // "MyApp"
The whitelist is collected automatically: every environment variable prefixed with VITE_ is
included, no manual key list needed.
Configuration
Optionally publish the config:
php artisan vendor:publish --tag=vite-env-config
config/vite-env.php:
route— the endpoint path, defaults to/vite-env.js.global— the global JS variable name, defaults to__ENV__.prefix— the environment variable prefix, defaults toVITE_.version— a random string (Str::random(8)), generated when the config file is loaded.
route/global/prefix are not read via env() — they're structural settings for the package
itself, not deploy secrets, so it's simpler to edit them directly in the published PHP file.
They're deliberately not tied to .env, otherwise a VITE_...-style variable for configuring the
package itself would fall under the prefix filter and leak into the public whitelist.
Variable interpolation in .env. If you use VITE_TMP=${APP_TMP}, the value is resolved by
Laravel/phpdotenv before our config file runs — the whitelist gets the already-resolved value.
This only works if APP_TMP is defined earlier in .env (dotenv parses top to bottom) or already
set as a real OS/container environment variable.
How it works with config:cache and Docker
The variable whitelist is collected once, when the config file loads — the same moment that
happens during php artisan config:cache, when the whole application boots and the result gets
baked into bootstrap/cache/config.php. After that, the runtime only reads config() — .env
and getenv() are never touched while handling a request. That's why .env is only needed at
image build time (when config:cache runs); it may not exist at all in an immutable container at
runtime.
The endpoint is fully public (no middleware/auth) — safety comes from the fact that only
VITE_-prefixed variables are included, i.e. values explicitly meant for the frontend.
Under Laravel Octane, the config file loads once per worker boot, not per request — so version
and the whitelist stay constant for the worker's lifetime even without config:cache. This
matches the cached-config behavior above, and differs from the "recomputed on every request"
behavior described below for plain (non-Octane) local development.
Caching
/vite-env.js?v=<version> is served with Cache-Control: public, max-age=31536000, immutable —
the browser never re-requests it as long as the URL doesn't change (same idea as fingerprinted
Vite/webpack assets). version is baked into bootstrap/cache/config.php together with the
whitelist during php artisan config:cache and stays constant for the whole lifetime of a
deployment; on the next deploy (new .env → config:cache) a new random version is generated —
the URL changes and the browser fetches the new JS.
Without config:cache and without Octane (plain local development), config/vite-env.php is
recomputed on every request — version changes every time, so browser caching effectively doesn't
apply, which is expected in dev.
Always use @viteEnv (or route('vite-env.js') . '?v=' . config('vite-env.version')), never a
bare /vite-env.js without ?v= — without a version in the URL, the browser would cache the
config forever with no way to invalidate it.
License
MIT, see LICENSE.