Frontend Apps¶
Scaffold a frontend with your framework CLI under apps/, and kernstack deploy builds it and publishes it to a bucket. On deploy, the CLI runs build, copies out into the bucket seed, and uploads it. It then writes kernstack-routes.json (route URLs plus the core origin) into the bucket — fetch it at runtime instead of baking URLs into the build. Field reference: IaC — Frontend apps.
Wire-up¶
Every static frontend needs three pieces in stack.json:
apps[]— build command,out, andseedpathresources.buckets— bucket that receives the seed uploadgateway.routes[]—"type": "prefix","default_key": "index.html",target.kind: "bucket"
{
"apps": [
{
"name": "web",
"build": "npm ci --prefix apps/web && npm run build --prefix apps/web",
"out": "apps/web/www",
"seed": "seed/buckets/www"
}
],
"gateway": {
"routes": [
{
"name": "www",
"type": "prefix",
"default_key": "index.html",
"cdn_enabled": true,
"target": { "kind": "bucket", "name": "www" }
}
]
},
"resources": {
"buckets": [{ "name": "www" }]
}
}
- Use
"type": "prefix": a bucket route without it serves a single object, so/and assets 404. - Set
seedwhen the app name (web) differs from the bucket (www); the default isseed/buckets/{app-name}. - Don't commit build outputs (
www/,dist/,node_modules/); the scaffolded.gitignorecovers them. - Redeploy one frontend without a stack apply:
kernstack deploy --only app:web(add--purge-cdnto purge only that bucket's CDN routes). The bucket and routes must already exist from a full deploy. - Per environment, override
apps[].buildin a profile overlay. - Upload files with presigned URLs returned by a function; the browser
PUTs straight to the bucket. Resolve presigned/v1/buckets/…paths against thecoreorigin inkernstack-routes.json, not the function URL.
Framework examples¶
Scaffold into apps/web and set out to the framework's build directory.
Static SPA with @sveltejs/adapter-static (fallback index.html). kernstack init --demo vendors this layout under apps/web. To hand-wire the same stack:
Point the adapter at build.
Blank starter with Capacitor (web + iOS + Android). Kernstack publishes the web build only; native shells stay local (npx cap sync, Xcode / Android Studio).
mkdir -p apps
cd apps
ionic start web blank --type=angular-standalone --capacitor --no-interactive
cd web
npx cap add ios
npx cap add android
Confirm capacitor.config.ts has webDir: 'www' and the Angular build outputPath is www.
{
"apps": [
{
"name": "web",
"build": "npm ci --prefix apps/web && npm run build --prefix apps/web",
"out": "apps/web/www",
"seed": "seed/buckets/www"
}
]
}
Local native workflow after a web build: npx cap sync then npx cap open ios / npx cap open android.
Set projects.web.architect.build.options.outputPath to www (or keep dist/web/browser and point out at that path).
Default output is dist.
{
"apps": [
{
"name": "web",
"build": "npm ci --prefix apps/web && npm run build --prefix apps/web",
"out": "apps/web/dist",
"seed": "seed/buckets/www"
}
]
}
Client-side routing works out of the box: on a prefix route with an HTML default_key, unknown extensionless (or .html) paths are answered with default_key and HTTP 200, so deep links load the app. Missing assets (.js, .css, images) still return 404. Uploading errors/404.html switches the route to real 404s and turns this fallback off — see Not-found pages.
Static export only. SSR / Node servers belong on a worker, not apps[].
In next.config:
/** @type {import('next').NextConfig} */
const nextConfig = { output: 'export' }
module.exports = nextConfig
No framework and no apps[]. Put files under seed/buckets/www/ and declare the bucket + prefix route only.
Native apps and SSR¶
- Native shells (
ios/,android/) stay underapps/without anapps[]entry; only declared entries build. - For SSR (Next.js server mode, Nuxt, Remix), run the app as a worker with
"src": "apps/web".