Part 4: SPA Runtime Config¶
Replace build-time environment variables with runtime configuration so a single Docker image works across all environments without rebuilding.
What you'll learn¶
- The difference between build-time and runtime configuration for SPAs
- Enabling
type: spaandspa_rootinappmecca.yaml - Reading
window.__APPMECCA_CONFIG__in SvelteKit - Managing environment variables with
mecca varcommands - The
PUBLIC_*naming convention for frontend-visible variables
What you'll need¶
- The full-stack app from Part 3 deployed and working
The problem with build-time config¶
In Part 3 you passed VITE_API_URL as a Docker build arg. This means:
- The URL is baked into the JavaScript bundle at build time
- Promoting the same image from
devtostagingrequires a rebuild - Every environment-specific change triggers a new build
AppMecca solves this with SPA runtime config injection.
How it works¶
When you set type: spa on a service, AppMecca generates a file called __appmecca_config.js inside the container's spa_root directory before starting the process. This file sets window.__APPMECCA_CONFIG__ with all PUBLIC_* environment variables.
The generated file looks like this:
window.__APPMECCA_CONFIG__ = {
PUBLIC_API_URL: "https://todo-app-backend-yourgroup.dev.example.com",
PUBLIC_APP_TITLE: "Todo App"
};
Update appmecca.yaml¶
Change the frontend service to use type: spa:
database:
enabled: true
containers:
- name: backend
dockerfile: backend/Dockerfile
context: backend/
- name: frontend
dockerfile: frontend/Dockerfile
context: frontend/
deploy:
- name: backend
container: backend
port: 3000
path: /api
health_check:
path: /health
- name: frontend
container: frontend
type: spa
command: 'nginx -g "daemon off;"' # serves the built bundle from a container
spa_root: /usr/share/nginx/html
port: 3000
health_check:
path: /
environment:
PUBLIC_API_URL: "${service:backend}"
PUBLIC_APP_TITLE: "Todo App"
Removed build arg
The args block on the frontend container is gone. The API URL is now injected at deploy time, not build time.
Add the config script to your HTML¶
Edit frontend/src/app.html to include the config script before SvelteKit's own scripts:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="/__appmecca_config.js"></script>
%sveltekit.head%
</head>
<body data-sveltekit-prerender-data>
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
Create a config helper¶
Create frontend/src/lib/config.js:
/**
* Read a runtime config value injected by AppMecca.
* Falls back to import.meta.env for local development.
* @param {string} key - Config key (e.g. "PUBLIC_API_URL")
* @param {string} [fallback=""] - Default value
* @returns {string}
*/
export function getConfig(key, fallback = "") {
if (typeof window !== "undefined" && window.__APPMECCA_CONFIG__) {
return window.__APPMECCA_CONFIG__[key] ?? fallback;
}
return import.meta.env[`VITE_${key}`] ?? fallback;
}
Local development
The helper falls back to import.meta.env so you can still use a .env file with VITE_PUBLIC_API_URL=http://localhost:3000 during local development.
Update the Svelte page¶
Replace the hardcoded API URL in +page.svelte:
<script>
import { onMount } from "svelte";
import { getConfig } from "$lib/config.js";
const API_URL = getConfig("PUBLIC_API_URL");
let todos = [];
let newTitle = "";
onMount(async () => {
const res = await fetch(`${API_URL}/api/todos`);
todos = await res.json();
});
// ... rest of the component unchanged
</script>
Manage environment variables¶
Set, list, and remove environment variables from the CLI. The app name is the first argument:
# Set one or more variables
mecca var set todo-app PUBLIC_APP_TITLE="My Todos"
mecca var set todo-app PUBLIC_APP_TITLE="My Todos" PUBLIC_FEATURE_FLAG=on
# List all variables, grouped by branch
mecca var list todo-app
mecca var list todo-app -b main --dotenv > .env # .env lines; needs one branch
# Remove a variable
mecca var unset todo-app PUBLIC_APP_TITLE
var set writes to every branch by default
With no -b, the value lands on all of the app's branches. Narrow it explicitly when that is not what you want:
Redeploy required
Changing environment variables does not take effect until you redeploy. The config file is generated at container start time. A --no-build deploy is enough:
Rebuild and deploy¶
Since you changed the Dockerfile and removed the build arg, rebuild:
After deployment, verify the config is injected by visiting https://todo-app-frontend-yourgroup.dev.example.com/__appmecca_config.js in your browser.
What's next¶
In Part 5: Background Workers, you will add a BullMQ worker for background job processing and learn about portless services.