config.ts - Configuration Runtime
Localisation : frontend/src/lib/config.ts
Gère la configuration runtime de l’application, notamment l’URL de l’API backend avec support multi-environnements.
Principe Runtime Configuration
Le frontend Astro est statique (SSG), mais nécessite une configuration runtime pour s’adapter aux environnements (dev, staging, prod) sans rebuild.
Problème avec Build-Time Config :
// ❌ MAUVAIS : Hard-codé au build
const API_URL = "https://api.koprogo.com/api/v1";
// Nécessite rebuild pour chaque environnement !
Solution Runtime Config :
// ✅ BON : Configuration injectable au runtime
const API_URL = window.__ENV__?.API_URL || fallback;
Configuration API_URL
getApiUrl()
Fonction qui résout l’URL API avec fallback cascade.
const getApiUrl = (): string => {
// 1. Runtime : window.__ENV__.API_URL (injecté par Docker/Ansible)
if (typeof window !== "undefined" && window.__ENV__?.API_URL) {
return window.__ENV__.API_URL;
}
// 2. Build-time : import.meta.env.PUBLIC_API_URL (.env)
if (typeof import.meta !== "undefined" && import.meta.env) {
return import.meta.env.PUBLIC_API_URL || "http://127.0.0.1:8080/api/v1";
}
// 3. Fallback : localhost développement
return "http://127.0.0.1:8080/api/v1";
};
export const API_URL = getApiUrl();
Ordre de Priorité :
window.__ENV__.API_URL (runtime) ← GitOps injecte ici
import.meta.env.PUBLIC_API_URL (build-time)
http://127.0.0.1:8080/api/v1 (fallback dev)
Injection Runtime via window.__ENV__
Fichier public/config.js :
// public/config.js
window.__ENV__ = {
API_URL: "https://api.koprogo.com/api/v1"
};
Chargement dans Layout :
---
// src/layouts/Layout.astro
---
<html>
<head>
<script is:inline src="/config.js"></script>
</head>
<body>
<slot />
</body>
</html>
⚠️ Attribut is:inline : Force le chargement synchrone avant app Svelte.
Injection Docker/Ansible
docker-compose.yml :
services:
frontend:
image: koprogo-frontend:latest
environment:
- API_URL=https://api.example.com/api/v1
command: >
sh -c "
echo 'window.__ENV__ = { API_URL: \"${API_URL}\" };' > /app/public/config.js &&
nginx -g 'daemon off;'
"
Ansible Template :
# ansible/roles/frontend/templates/config.js.j2
window.__ENV__ = {
API_URL: "{{ api_url }}"
};
# ansible/roles/frontend/tasks/main.yml
- name: Generate runtime config
template:
src: config.js.j2
dest: /var/www/koprogo/public/config.js
vars:
api_url: "{{ lookup('env', 'API_URL') | default('https://api.koprogo.com/api/v1') }}"
GitOps Flow :
GitHub Push
↓
gitops-deploy.sh
↓
Ansible génère public/config.js avec API_URL
↓
window.__ENV__.API_URL disponible au runtime
↓
api.ts utilise cette URL
apiEndpoint(path)
Helper pour construire URLs complètes des endpoints.
export const apiEndpoint = (path: string): string => {
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
const apiUrl = getApiUrl(); // Toujours appeler pour avoir la valeur runtime
return `${apiUrl}${normalizedPath}`;
};
Exemples :
apiEndpoint('/auth/login')
// → "https://api.koprogo.com/api/v1/auth/login"
apiEndpoint('buildings')
// → "https://api.koprogo.com/api/v1/buildings"
apiEndpoint('/buildings/123')
// → "https://api.koprogo.com/api/v1/buildings/123"
⚠️ Toujours appeler getApiUrl() : Ne pas cacher dans une constante globale, car window.__ENV__ peut être défini après le premier appel.
Configuration Build-Time (.env)
Fichier frontend/.env :
# URL de l'API backend (build-time)
PUBLIC_API_URL=http://127.0.0.1:8080/api/v1
⚠️ Préfixe PUBLIC_ : Variables exposées côté client doivent avoir le préfixe PUBLIC_ dans Astro.
Environnements :
# frontend/.env.development
PUBLIC_API_URL=http://127.0.0.1:8080/api/v1
# frontend/.env.staging
PUBLIC_API_URL=https://api-staging.koprogo.com/api/v1
# frontend/.env.production
PUBLIC_API_URL=https://api.koprogo.com/api/v1
Build avec environnement :
# Development
npm run dev
# Production
npm run build
# Staging
npm run build -- --mode staging
Type Safety window.__ENV__
Déclaration TypeScript :
declare global {
interface Window {
__ENV__?: {
API_URL?: string;
};
}
}
Cela permet l’autocomplétion et typage :
window.__ENV__.API_URL // ✅ TypeScript OK
Utilisation dans api.ts
Le module api.ts utilise config.ts :
// frontend/src/lib/api.ts
import { API_URL } from "./config";
const API_BASE_URL = API_URL;
export async function apiFetch(endpoint: string, options: RequestInit = {}) {
const url = endpoint.startsWith("http")
? endpoint
: `${API_BASE_URL}${endpoint}`;
// ...
}
Debugging Configuration
Console Log au chargement :
// frontend/src/lib/config.ts
const apiUrl = getApiUrl();
console.log('🔧 API URL:', apiUrl);
console.log('📦 window.__ENV__:', window.__ENV__);
console.log('🏗️ import.meta.env.PUBLIC_API_URL:', import.meta.env.PUBLIC_API_URL);
export const API_URL = apiUrl;
Component Debug :
<script lang="ts">
import { API_URL } from '../lib/config';
</script>
<div class="debug-panel">
<h3>Configuration</h3>
<ul>
<li>API_URL: {API_URL}</li>
<li>window.__ENV__: {JSON.stringify(window.__ENV__)}</li>
</ul>
</div>
Tests Configuration
// tests/unit/config.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
describe('config', () => {
beforeEach(() => {
delete (window as any).__ENV__;
vi.resetModules();
});
it('should use window.__ENV__.API_URL if available', async () => {
(window as any).__ENV__ = { API_URL: 'https://test.com/api' };
const { API_URL } = await import('../src/lib/config');
expect(API_URL).toBe('https://test.com/api');
});
it('should fallback to import.meta.env', async () => {
const { API_URL } = await import('../src/lib/config');
expect(API_URL).toContain('127.0.0.1');
});
});
Configuration Multi-Variables
Extension future : Ajouter d’autres variables runtime
declare global {
interface Window {
__ENV__?: {
API_URL?: string;
SENTRY_DSN?: string;
FEATURE_FLAGS?: {
enableOfflineMode?: boolean;
enablePWA?: boolean;
};
};
}
}
// public/config.js
window.__ENV__ = {
API_URL: "https://api.koprogo.com/api/v1",
SENTRY_DSN: "https://...",
FEATURE_FLAGS: {
enableOfflineMode: true,
enablePWA: false
}
};
Bonnes Pratiques
Toujours utiliser getApiUrl() : Ne pas cacher dans constante
// ❌ MAUVAIS const url = API_URL; // Évalué une seule fois // ✅ BON const url = getApiUrl(); // Évalué à chaque appel
PUBLIC_ prefix : Variables Astro exposées côté client
PUBLIC_API_URL=... # ✅ Visible côté client API_URL=... # ❌ Uniquement côté serveur
is:inline pour config.js : Chargement synchrone avant app
<script is:inline src="/config.js"></script>
Validation runtime : Vérifier que window.__ENV__ existe
if (typeof window !== "undefined" && !window.__ENV__) { console.warn('⚠️ window.__ENV__ not defined, using fallback'); }
Ne pas commit public/config.js : Fichier généré au déploiement
# .gitignore public/config.js
Avantages Runtime Config
✅ Un seul build pour tous les environnements
Build une fois, déployer partout
Pas de rebuild pour staging/prod
✅ GitOps friendly
Ansible génère config.js au déploiement
Pas de secrets dans le build
✅ Docker friendly
Variables d’environnement Docker → config.js
Image Docker réutilisable
✅ Zero downtime config updates
Modifier config.js sans rebuild
Redémarrer Nginx uniquement
Limitations
❌ Pas de SSR : Configuration runtime uniquement côté client
❌ window.__ENV__ non disponible au build : SSG ne peut pas l’utiliser
❌ Sécurité : Variables exposées dans le bundle JavaScript (pas de secrets !)
Références
API Client :
frontend/src/lib/api.tsLayout :
frontend/src/layouts/Layout.astroDocker Compose :
deploy/production/docker-compose.ymlAnsible :
infrastructure/ansible/roles/koprogo/templates/config.js.j2