<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ファース</title>
    <description>The latest articles on DEV Community by ファース (@pyyupsk).</description>
    <link>https://dev.to/pyyupsk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1706151%2F42e4d641-1e36-4de5-9186-c3b126d58710.png</url>
      <title>DEV Community: ファース</title>
      <link>https://dev.to/pyyupsk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pyyupsk"/>
    <language>en</language>
    <item>
      <title>Why I Built a New Vite Env Plugin</title>
      <dc:creator>ファース</dc:creator>
      <pubDate>Wed, 08 Apr 2026 13:09:28 +0000</pubDate>
      <link>https://dev.to/pyyupsk/why-i-built-a-new-vite-env-plugin-3c8f</link>
      <guid>https://dev.to/pyyupsk/why-i-built-a-new-vite-env-plugin-3c8f</guid>
      <description>&lt;p&gt;The four problems with plain Vite environment variables — and the plugin I wrote to fix them.&lt;/p&gt;

&lt;p&gt;This is what environment variables look like in a Vite project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_PORT&lt;/span&gt;     &lt;span class="c1"&gt;// "5173" — string, not number&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_DARK&lt;/span&gt;     &lt;span class="c1"&gt;// "true" — string, not boolean&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_API_URL&lt;/span&gt;  &lt;span class="c1"&gt;// string | undefined — no validation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every value is a raw string. There's no validation, no server/client&lt;br&gt;
boundary, no leak detection. The only way to get types is a&lt;br&gt;
&lt;code&gt;vite-env.d.ts&lt;/code&gt; you write and maintain by hand.&lt;/p&gt;

&lt;p&gt;Four problems. I built a plugin that fixes all of them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem 1: Everything is a string
&lt;/h2&gt;

&lt;p&gt;You coerce values yourself. A forgotten &lt;code&gt;Number()&lt;/code&gt; or a&lt;br&gt;
&lt;code&gt;=== true&lt;/code&gt; on a string is a quiet bug that passes every check until&lt;br&gt;
it doesn't.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem 2: No server/client boundary
&lt;/h2&gt;

&lt;p&gt;Variables prefixed with &lt;code&gt;VITE_&lt;/code&gt; go to the client. Everything else&lt;br&gt;
stays server-side. That's the convention. There's no enforcement,&lt;br&gt;
no explicit split, no warning if you cross the line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// shared/config.ts — imported in both server and client code&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt; &lt;span class="c1"&gt;// silently bundled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If server and client code share a module, secrets travel with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: No leak detection
&lt;/h2&gt;

&lt;p&gt;Even careful code can leak. Bundlers inline values. After&lt;br&gt;
tree-shaking and minification, the literal string value of a server&lt;br&gt;
secret can appear inside a client chunk — no import reference, just&lt;br&gt;
the raw value embedded in compiled output. Nothing checks for this.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem 4: Manual type maintenance
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite-env.d.ts — written and updated by hand&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ImportMetaEnv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;VITE_API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;VITE_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="c1"&gt;// someone added VITE_FEATURE_FLAG last week and forgot this file&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These drift. The variable is in &lt;code&gt;.env&lt;/code&gt;. TypeScript doesn't complain.&lt;br&gt;
The mismatch goes unnoticed.&lt;/p&gt;
&lt;h2&gt;
  
  
  What already exists
&lt;/h2&gt;

&lt;p&gt;Two tools address parts of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Julien-R44/vite-plugin-validate-env" rel="noopener noreferrer"&gt;@julr/vite-plugin-validate-env&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
validates env at build time and injects values into &lt;code&gt;import.meta.env&lt;/code&gt;.&lt;br&gt;
Supports Standard Schema (Zod, Valibot, ArkType) and a lightweight&lt;br&gt;
built-in validator. Zero runtime overhead. It does exactly what it&lt;br&gt;
promises — validation. It doesn't split server/client variables,&lt;br&gt;
doesn't provide virtual modules, and doesn't detect leaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/t3-oss/t3-env" rel="noopener noreferrer"&gt;@t3-oss/env-core&lt;/a&gt;&lt;/strong&gt; validates&lt;br&gt;
at import time and provides runtime server/client protection via a&lt;br&gt;
Proxy. Platform presets for Vercel, Railway, Netlify, and others.&lt;br&gt;
The &lt;code&gt;extends&lt;/code&gt; system works well for monorepos. The trade-offs:&lt;br&gt;
&lt;code&gt;runtimeEnv&lt;/code&gt; requires listing every variable twice, there's no&lt;br&gt;
build-time leak detection, and it's framework-agnostic — it can't&lt;br&gt;
hook into Vite's build pipeline.&lt;/p&gt;

&lt;p&gt;Both are good tools. Neither solves all four problems for Vite.&lt;/p&gt;
&lt;h2&gt;
  
  
  One file, everything derived
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/pyyupsk/vite-env" rel="noopener noreferrer"&gt;&lt;code&gt;@vite-env/core&lt;/code&gt;&lt;/a&gt;. One&lt;br&gt;
&lt;code&gt;env.ts&lt;/code&gt; file. The plugin handles validation, virtual modules, type&lt;br&gt;
generation, and leak detection from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// env.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineEnv&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vite-env/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEnv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;DB_POOL_SIZE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;VITE_API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;VITE_APP_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;VITE_DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringbool&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;VITE_LOG_LEVEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ViteEnv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vite-env/core/plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ViteEnv&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt; runs at build start. Missing or malformed variables&lt;br&gt;
fail immediately with a list of every problem at once. During dev,&lt;br&gt;
&lt;code&gt;.env&lt;/code&gt; changes revalidate — terminal warning, no crash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual modules&lt;/strong&gt; enforce the split:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client code&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;virtual:env/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_API_URL&lt;/span&gt;   &lt;span class="c1"&gt;// string&lt;/span&gt;
&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_DEBUG&lt;/span&gt;     &lt;span class="c1"&gt;// boolean — coerced, not "true"&lt;/span&gt;
&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;   &lt;span class="c1"&gt;// TypeScript error — doesn't exist here&lt;/span&gt;

&lt;span class="c1"&gt;// Server/SSR code&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;virtual:env/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;   &lt;span class="c1"&gt;// string&lt;/span&gt;
&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JWT_SECRET&lt;/span&gt;     &lt;span class="c1"&gt;// string&lt;/span&gt;
&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_API_URL&lt;/span&gt;   &lt;span class="c1"&gt;// also available&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Leak detection&lt;/strong&gt; scans every client chunk at &lt;code&gt;generateBundle&lt;/code&gt; for&lt;br&gt;
the literal string values of server variables. If &lt;code&gt;DATABASE_URL&lt;/code&gt;'s&lt;br&gt;
value appears anywhere in the browser bundle, the build fails with&lt;br&gt;
the chunk name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type generation&lt;/strong&gt; writes &lt;code&gt;vite-env.d.ts&lt;/code&gt; on every build start.&lt;br&gt;
Add a variable to &lt;code&gt;env.ts&lt;/code&gt;, the declaration file updates. Nothing&lt;br&gt;
to maintain by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime access protection&lt;/strong&gt; uses Vite 8's Environment API. If&lt;br&gt;
client code imports &lt;code&gt;virtual:env/server&lt;/code&gt;, the plugin intercepts it&lt;br&gt;
during the build. Three modes: &lt;code&gt;'error'&lt;/code&gt; (hard fail), &lt;code&gt;'warn'&lt;/code&gt;&lt;br&gt;
(default — logs and exits with code 1), &lt;code&gt;'stub'&lt;/code&gt; (returns a module&lt;br&gt;
that throws at access time, useful for isomorphic framework files).&lt;br&gt;
The default changes to &lt;code&gt;'error'&lt;/code&gt; in 1.0.0 — set it explicitly now&lt;br&gt;
if you're already using the plugin.&lt;/p&gt;
&lt;h2&gt;
  
  
  Standard Schema
&lt;/h2&gt;

&lt;p&gt;If you prefer Valibot, ArkType, or any other Standard Schema&lt;br&gt;
validator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineStandardEnv&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vite-env/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;valibot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineStandardEnv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;VITE_API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="na"&gt;VITE_APP_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same plugin, same virtual modules, same leak detection. The generated&lt;br&gt;
&lt;code&gt;.d.ts&lt;/code&gt; types are less specific than with Zod — Standard Schema&lt;br&gt;
doesn't expose the same type introspection — but everything else&lt;br&gt;
works identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform presets
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineEnv&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vite-env/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vercel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vite-env/core/presets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEnv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;vercel&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;VITE_API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available: &lt;code&gt;vercel&lt;/code&gt;, &lt;code&gt;railway&lt;/code&gt;, &lt;code&gt;netlify&lt;/code&gt;. Your definitions take&lt;br&gt;
precedence over preset values.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I chose not to do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No runtime Proxy.&lt;/strong&gt; t3-env throws at runtime when you access a&lt;br&gt;
server variable from the client. I chose build-time enforcement&lt;br&gt;
instead. Virtual modules and TypeScript catch it before the code&lt;br&gt;
runs. If you bypass TypeScript deliberately, there's no runtime&lt;br&gt;
throw — that's the trade-off, and I think it's the right one for&lt;br&gt;
a build tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No &lt;code&gt;runtimeEnv&lt;/code&gt; mapping.&lt;/strong&gt; t3-env needs this because Next.js&lt;br&gt;
tree-shakes &lt;code&gt;process.env&lt;/code&gt; access and requires explicit references&lt;br&gt;
to include variables in the bundle. Vite doesn't have this problem.&lt;br&gt;
The plugin calls &lt;code&gt;loadEnv()&lt;/code&gt; directly and serves everything through&lt;br&gt;
virtual modules. You define a variable once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No framework adapters.&lt;/strong&gt; This is Vite-specific. It uses&lt;br&gt;
&lt;code&gt;configResolved&lt;/code&gt;, &lt;code&gt;buildStart&lt;/code&gt;, &lt;code&gt;resolveId&lt;/code&gt;, &lt;code&gt;load&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;generateBundle&lt;/code&gt;, &lt;code&gt;configureServer&lt;/code&gt;, and Vite 8's Environment API.&lt;br&gt;
If you're on Next.js or Nuxt without Vite, t3-env is the right&lt;br&gt;
tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validate without starting the dev server&lt;/span&gt;
npx vite-env check

&lt;span class="c"&gt;# Generate .env.example from your schema&lt;/span&gt;
npx vite-env generate

&lt;span class="c"&gt;# Regenerate vite-env.d.ts manually&lt;/span&gt;
npx vite-env types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vite-env generate&lt;/code&gt; is the most useful one onboarding-wise. Run it&lt;br&gt;
once and new developers get a documented &lt;code&gt;.env.example&lt;/code&gt; with types,&lt;br&gt;
defaults, and required markers — all from the same schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/pyyupsk/vite-env" rel="noopener noreferrer"&gt;pyyupsk/vite-env&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://pyyupsk.github.io/vite-env/" rel="noopener noreferrer"&gt;pyyupsk.github.io/vite-env&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @vite-env/core zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something doesn't work or the docs are unclear, open an issue.&lt;/p&gt;

</description>
      <category>vite</category>
      <category>typescript</category>
      <category>zod</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
