2.6 — Variáveis de Ambiente e Modos

Configure diferentes ambientes (dev, staging, produção) com variáveis seguras.

Objetivos da Aula

  • Entender o sistema de variáveis de ambiente do Vite
  • Criar arquivos .env para diferentes ambientes
  • Diferenciar variáveis públicas e privadas
  • Usar modos customizados

Como Variáveis de Ambiente Funcionam

O Vite usa arquivos .env para definir variáveis de ambiente:

ARQUIVOS .env
.env Carregado em TODOS os casos .env.local Carregado em todos, ignorado pelo git .env.[mode] Carregado apenas no modo especificado .env.[mode].local Carregado no modo, ignorado pelo git

Ordem de prioridade (maior para menor):

  1. 1 .env.[mode].local
  2. 2 .env.[mode]
  3. 3 .env.local
  4. 4 .env

Modos Padrão

ComandoModo
vite / vite devdevelopment
vite buildproduction
vite previewproduction

Criando Arquivos .env

.env — Base (todos os ambientes)

# .env
# Variáveis compartilhadas entre todos os ambientes

# PÚBLICAS (acessíveis no cliente)
VITE_APP_NAME=Dashboard Vite
VITE_APP_VERSION=1.0.0

# PRIVADAS (só no servidor/build)
DATABASE_URL=postgresql://localhost:5432/mydb
SECRET_KEY=minha-chave-secreta

.env.development — Desenvolvimento

# .env.development
# Sobrescreve .env quando mode=development

VITE_API_URL=http://localhost:8080/api
VITE_DEBUG=true

# Banco de dev
DATABASE_URL=postgresql://localhost:5432/mydb_dev

.env.production — Produção

# .env.production
# Sobrescreve .env quando mode=production

VITE_API_URL=https://api.meuprojeto.com
VITE_DEBUG=false

# Banco de produção (nunca commite senhas reais!)
DATABASE_URL=postgresql://prod-server:5432/mydb_prod

.env.local — Local (não commitado)

# .env.local
# NUNCA vai para o git - para secrets locais

# Chaves pessoais de API
VITE_MAPS_API_KEY=sua-chave-pessoal-aqui
GITHUB_TOKEN=ghp_xxxxxxxxxxxx

.gitignore

# .gitignore
.env.local
.env.*.local

Acessando Variáveis

No Cliente (VITE_ prefix)

Importante: Apenas variáveis com prefixo VITE_ são expostas ao cliente!

Cuidado com VITE_
Nunca use o prefixo VITE_ para segredos como senhas, tokens ou chaves privadas. Tudo com VITE_ vai parar no bundle do navegador e qualquer pessoa pode ver no DevTools.
// Funciona - tem prefixo VITE_
console.log(import.meta.env.VITE_APP_NAME)
console.log(import.meta.env.VITE_API_URL)

// undefined - sem prefixo VITE_
// DATABASE_URL e SECRET_KEY nao sao expostas
console.log(import.meta.env.DATABASE_URL)
// retorna undefined
console.log(import.meta.env.SECRET_KEY)
// retorna undefined

Variáveis Automáticas

// Variáveis sempre disponíveis
// 'development' ou 'production'
import.meta.env.MODE
// URL base (config.base)
import.meta.env.BASE_URL
// true se production
import.meta.env.PROD
// true se development
import.meta.env.DEV
// true se server-side rendering
import.meta.env.SSR

Exemplo Prático

// src/config.ts

interface AppConfig {
  appName: string
  apiUrl: string
  debug: boolean
  isDev: boolean
  isProd: boolean
  mode: string
}

export const config: AppConfig = {
  appName: import.meta.env.VITE_APP_NAME,
  apiUrl: import.meta.env.VITE_API_URL,
  debug: import.meta.env.VITE_DEBUG === 'true',
  isDev: import.meta.env.DEV,
  isProd: import.meta.env.PROD,
  mode: import.meta.env.MODE
}

// Uso condicional
if (config.isDev) {
  console.log('Modo desenvolvimento')
  console.log('Config:', config)
}

// API URL dinâmica
async function fetchData(
  endpoint: string
): Promise<unknown> {
  const url = `${config.apiUrl}/${endpoint}`
  return fetch(url).then(r => r.json())
}

TypeScript e Variáveis de Ambiente

Tipagem das Variáveis

Crie um arquivo de tipos:

// src/vite-env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_APP_NAME: string
  readonly VITE_APP_VERSION: string
  readonly VITE_API_URL: string
  readonly VITE_DEBUG: string
  readonly VITE_MAPS_API_KEY?: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

Agora você tem autocomplete!

// TypeScript sabe que essas variáveis existem
const apiUrl = import.meta.env.VITE_API_URL
// string
const debug = import.meta.env.VITE_DEBUG
// string
SvelteKit tem algo melhor
No SvelteKit, prefira usar $env/static/public e $env/static/private em vez de import.meta.env. Esses módulos sao type-safe, validados em build time, e o SvelteKit garante que variaveis privadas nunca vazem para o cliente.

Modos Customizados

Criando um Modo Staging

# .env.staging
VITE_APP_NAME=Dashboard Vite (Staging)
VITE_API_URL=https://staging-api.meuprojeto.com
VITE_DEBUG=true
// package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:staging": "vite build --mode staging",
    "preview": "vite preview"
  }
}

Modo de Preview/QA

# .env.preview
VITE_APP_NAME=Dashboard Vite (Preview)
VITE_API_URL=https://preview-api.meuprojeto.com
VITE_FEATURE_FLAGS={"newUI":true,"betaFeatures":true}
{
  "scripts": {
    "build:preview": "vite build --mode preview"
  }
}

Usando no vite.config.ts

Carregando Variáveis na Config

// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import type { UserConfig } from 'vite'

export default defineConfig(({ mode }) => {
  // Carrega variáveis de ambiente
  // process.cwd() = diretório atual
  // '' = prefixo vazio (carrega TODAS)
  const env = loadEnv(
    mode,
    process.cwd(),
    ''
  )

  console.log(`Modo: ${mode}`)
  console.log(`API URL: ${env.VITE_API_URL}`)

  return {
    define: {
      // Injeta variáveis customizadas
      __APP_VERSION__: JSON.stringify(
        env.VITE_APP_VERSION
      )
    },

    server: {
      proxy: {
        '/api': {
          target: env.VITE_API_URL,
          changeOrigin: true
        }
      }
    }
  } satisfies UserConfig
})

Variáveis em Tempo de Build

// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  define: {
    // Disponíveis em todo o código
    __BUILD_TIME__: JSON.stringify(
      new Date().toISOString()
    ),
    __COMMIT_HASH__: JSON.stringify(
      process.env.COMMIT_SHA || 'local'
    )
  }
})
// No código
// declare para TypeScript reconhecer
declare const __BUILD_TIME__: string
declare const __COMMIT_HASH__: string

console.log(`Build: ${__BUILD_TIME__}`)
console.log(`Commit: ${__COMMIT_HASH__}`)

Substituição de Variáveis em HTML

index.html

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <title>%VITE_APP_NAME%</title>
    <meta
      name="version"
      content="%VITE_APP_VERSION%"
    >
  </head>
  <body>
    <div id="app"></div>
    <script
      type="module"
      src="/src/main.ts"
    ></script>
  </body>
</html>

O Vite substitui %VITE_*% automaticamente!


Segurança

Regras Importantes

SEGURANCA DE VARIAVEIS

VITE_ (publico) — Visivel no navegador, ok expor

  • URLs de API publicas
  • Nomes de app
  • Feature flags publicos
  • Chaves de API publicas (Google Maps, etc)

SEM PREFIXO (privado) — NUNCA exposto, seguro

  • Senhas de banco
  • Tokens de autenticacao
  • Chaves secretas
  • API keys privadas

NUNCA faca:

  • VITE_DATABASE_PASSWORD=...
  • VITE_STRIPE_SECRET_KEY=...
  • Commitar .env.local
O que acontece se eu colocar uma senha com VITE_ prefix?
A senha vai ser incluida no bundle JavaScript final e qualquer pessoa que abrir o DevTools do navegador consegue ver. Isso vale para qualquer variavel com prefixo VITE_ — ela e substituida em tempo de build como texto puro no codigo. Nunca coloque senhas, tokens secretos ou chaves privadas com esse prefixo.

Validação de Variáveis

// src/env.ts
// Valida que todas as variáveis necessárias existem

const requiredEnvVars: string[] = [
  'VITE_APP_NAME',
  'VITE_API_URL'
]

for (const envVar of requiredEnvVars) {
  if (!import.meta.env[envVar]) {
    throw new Error(
      `Variável de ambiente ${envVar} não definida!`
    )
  }
}

interface Env {
  appName: string
  apiUrl: string
  debug: boolean
  isDev: boolean
  isProd: boolean
}

export const env: Env = {
  appName: import.meta.env.VITE_APP_NAME,
  apiUrl: import.meta.env.VITE_API_URL,
  debug: import.meta.env.VITE_DEBUG === 'true',
  isDev: import.meta.env.DEV,
  isProd: import.meta.env.PROD
}

Mini-Projeto: Configuração Multi-Ambiente

Vamos configurar nosso Dashboard para múltiplos ambientes:

Passo 1: Criar arquivos .env

# .env
VITE_APP_NAME=Dashboard Vite
VITE_APP_VERSION=1.0.0
# .env.development
VITE_API_URL=http://localhost:3001
VITE_DEBUG=true
VITE_ENVIRONMENT=development
# .env.staging
VITE_API_URL=https://staging-api.example.com
VITE_DEBUG=true
VITE_ENVIRONMENT=staging
# .env.production
VITE_API_URL=https://api.example.com
VITE_DEBUG=false
VITE_ENVIRONMENT=production

Passo 2: Criar módulo de configuração

// src/config/env.ts

interface AppEnv {
  app: {
    name: string
    version: string
  }
  api: {
    url: string
  }
  flags: {
    debug: boolean
  }
  runtime: {
    mode: string
    isDev: boolean
    isProd: boolean
    environment: string
  }
}

// Validação
const required = [
  'VITE_APP_NAME',
  'VITE_API_URL'
]

for (const key of required) {
  if (!import.meta.env[key]) {
    console.error(
      `Variável ${key} não definida!`
    )
  }
}

// Exporta configuração tipada
export const env: AppEnv = {
  app: {
    name: import.meta.env.VITE_APP_NAME,
    version:
      import.meta.env.VITE_APP_VERSION || '0.0.0'
  },
  api: {
    url: import.meta.env.VITE_API_URL
  },
  flags: {
    debug:
      import.meta.env.VITE_DEBUG === 'true'
  },
  runtime: {
    mode: import.meta.env.MODE,
    isDev: import.meta.env.DEV,
    isProd: import.meta.env.PROD,
    environment:
      import.meta.env.VITE_ENVIRONMENT
      || 'unknown'
  }
}

// Log em desenvolvimento
if (env.flags.debug) {
  console.log('Configuração carregada:', env)
}

Passo 3: Componente de status do ambiente

<!-- src/components/EnvironmentBadge.svelte -->
<script lang="ts">
  import { env } from '$lib/config/env'

  const colors: Record<string, string> = {
    // verde
    development: '#22c55e',
    // amarelo
    staging: '#f59e0b',
    // vermelho
    production: '#ef4444'
  }

  const bgColor =
    colors[env.runtime.environment] || '#666'

  const label =
    `${env.runtime.environment} • v${env.app.version}`

  const visible =
    !(env.runtime.isProd && !env.flags.debug)
</script>

{#if visible}
  <div
    class="environment-badge"
    style="
      position: fixed;
      bottom: 1rem;
      right: 1rem;
      padding: 0.5rem 1rem;
      border-radius: 9999px;
      font-size: 0.75rem;
      font-weight: 600;
      text-transform: uppercase;
      background: {bgColor};
      color: white;
      z-index: 9999;
    "
  >
    {label}
  </div>
{/if}

Passo 4: Atualizar main.ts

<!-- src/App.svelte -->
<script lang="ts">
  import { env } from '$lib/config/env'
  import EnvironmentBadge
    from './components/EnvironmentBadge.svelte'

  // Atualiza título com nome do app
  document.title =
    `${env.app.name} - ${env.runtime.environment}`
</script>

<!-- conteúdo do app -->
<slot />
<EnvironmentBadge />

Passo 5: Scripts no package.json

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:staging": "vite build --mode staging",
    "preview": "vite preview",
    "preview:staging": "vite preview --mode staging"
  }
}

Desafio da Aula

Objetivo

Criar um sistema de feature flags baseado em variáveis de ambiente.

Instruções

  1. Crie uma variável VITE_FEATURE_FLAGS com JSON de features
  2. Crie um módulo que parseia e expõe as flags
  3. Use as flags para mostrar/esconder um card “Beta Features”

Exemplo de .env

VITE_FEATURE_FLAGS={"darkMode":true,"betaCard":true,"analytics":false}

Spec de Verificação

  • O módulo featureFlags exporta um objeto com as flags
  • Quando betaCard: true, um card especial aparece no dashboard
  • Quando betaCard: false, o card não aparece

Solução

Clique para ver a solução
// src/config/features.ts
const flagsJson =
  import.meta.env.VITE_FEATURE_FLAGS || '{}'

interface FeatureFlags {
  darkMode: boolean
  betaCard: boolean
  analytics: boolean
}

let flags: Partial<FeatureFlags> = {}
try {
  flags = JSON.parse(flagsJson)
} catch (e) {
  console.error(
    'Erro ao parsear feature flags:',
    e
  )
}

export const featureFlags: FeatureFlags = {
  darkMode: flags.darkMode ?? false,
  betaCard: flags.betaCard ?? false,
  analytics: flags.analytics ?? false
}

export function isEnabled(
  flag: keyof FeatureFlags
): boolean {
  return featureFlags[flag] === true
}
<!-- src/components/BetaCard.svelte -->
<script lang="ts">
  import { isEnabled }
    from '$lib/config/features'
</script>

{#if isEnabled('betaCard')}
  <div class="performance-card beta-card">
    <span class="beta-badge">BETA</span>
    <h3 class="card-title">
      Funcionalidade Beta
    </h3>
    <p>
      Esta é uma funcionalidade experimental!
    </p>
  </div>
{/if}
<!-- No componente pai -->
<script lang="ts">
  import BetaCard
    from './components/BetaCard.svelte'
</script>

<!-- Na área do grid de cards -->
<BetaCard />

Próxima aula: 2.7 — Build de Produção e Otimização