This guide covers configuring a SvelteKit application for static SPA generation using @sveltejs/adapter-static, setting up the development proxy, integrating @kodall/kodall-client, and deploying with @kodall/kodall-deploy.
Deployed on Kodall instance at /svelte
Currently running in shared demo mode with the public API key. Create a private 24-hour sandbox with 1-click to test creating, editing, and deleting records without interference.
Install @kodall/kodall-client, @kodall/kodall-deploy, and @sveltejs/adapter-static in your SvelteKit project:
npm install @kodall/kodall-client
npm install -D @kodall/kodall-deploy @sveltejs/adapter-static
pnpm add @kodall/kodall-client
pnpm add -D @kodall/kodall-deploy @sveltejs/adapter-static
yarn add @kodall/kodall-client
yarn add -D @kodall/kodall-deploy @sveltejs/adapter-static
bun add @kodall/kodall-client
bun add -d @kodall/kodall-deploy @sveltejs/adapter-static
Configure @sveltejs/adapter-static with an index.html fallback in svelte.config.js:
import adapter from '@sveltejs/adapter-static'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html'
})
}
}
Configure Vite's development proxy in vite.config.ts:
import { sveltekit } from '@sveltejs/kit/vite'
import { defineConfig } from 'vite'
import { kodallProxy } from '@kodall/kodall-deploy/vite'
export default defineConfig({
plugins: [
sveltekit(),
// Option A: Automatic Vite Plugin (Recommended)
kodallProxy() // Automatically resolves instance from default_env
]
// Option B: Manual Vite Proxy (Without Helper Plugin)
// server: {
// proxy: {
// '/auth': { target: 'https://dev.kodall.yourcompany.com', changeOrigin: true, secure: true },
// '/rest': { target: 'https://dev.kodall.yourcompany.com', changeOrigin: true, secure: true },
// '/storage': { target: 'https://dev.kodall.yourcompany.com', changeOrigin: true, secure: true }
// }
// }
})
kodallProxy() automatically resolves target URLs, custom proxy_paths, and proxy routes from default_env in your kodall-webapp.config.json. To target a specific environment, pass { env: 'staging' } or explore the Local Dev Proxy Suite.Create a shared instance of KodallClient:
import { KodallClient } from '@kodall/kodall-client'
export const client = new KodallClient()
new KodallClient() requires no hardcoded baseUrl or credentials.SvelteKit static builds are emitted to ./build:
{
"web_app_name": "Svelte App",
"web_app_path": "/",
"dist_path": "./build",
"default_env": "dev",
"environments": {
"dev": {
"type": "dev",
"instance": "https://dev.kodall.yourcompany.com",
"api_key": "dev-api-key-here"
},
"prod": {
"type": "prod",
"instance": "https://kodall.yourcompany.com",
"api_key": "prod-api-key-here"
}
}
}
https://docs-demo.kodall.io. Enter the base URL of your own Kodall instance deployed from the Kodall Cloud Console.Support both API Key (stateless bearer) and Username/Password (cookie session), restoring the session on page refresh with an anti-flicker loading state:
<script lang="ts">
import { onMount } from 'svelte'
import { KodallClient, isProblem } from '@kodall/kodall-client'
let isInitializing = true
let isAuthenticated = false
let authMode: 'apikey' | 'basic' = 'apikey'
let apiKey = ''
let username = ''
let password = ''
let loading = false
let authError = ''
function getClient(customKey?: string): KodallClient {
const keyToUse = customKey !== undefined ? customKey : (authMode === 'apikey' ? apiKey : undefined)
return new KodallClient({ apiKey: keyToUse || undefined })
}
// Option A: API Key
async function handleConnectApiKey(keyInput?: string) {
const targetKey = keyInput || apiKey
if (!targetKey.trim()) return
loading = true
authError = ''
try {
const client = getClient(targetKey)
const result = await client.fetch('FETCH todo (key, title, is_completed) LIMIT 1')
if (isProblem(result)) {
authError = `Authentication failed: ${(result as any).detail}`
} else {
isAuthenticated = true
authMode = 'apikey'
localStorage.setItem('kodall_is_logged_in', 'true')
localStorage.setItem('kodall_auth_mode', 'apikey')
localStorage.setItem('kodall_api_key', targetKey)
}
} finally {
loading = false
}
}
// Option B: Username / Password
async function handleLoginBasic(uInput?: string, pInput?: string) {
const u = uInput || username
const p = pInput || password
if (!u || !p) return
loading = true
authError = ''
try {
const client = getClient('')
const result = await client.auth({ user: u, password: p }, { roles: true })
if (isProblem(result)) {
authError = (result as any).detail || 'Login failed'
} else {
isAuthenticated = true
authMode = 'basic'
localStorage.setItem('kodall_is_logged_in', 'true')
localStorage.setItem('kodall_auth_mode', 'basic')
localStorage.setItem('kodall_username', u)
localStorage.setItem('kodall_password', p)
}
} finally {
loading = false
}
}
// Restore session on mount (anti-flicker splash)
onMount(async () => {
try {
const params = new URLSearchParams(window.location.search)
const urlApiKey = params.get('apiKey') || params.get('api_key')
const urlUser = params.get('user') || params.get('username')
const urlPass = params.get('password') || params.get('pass')
if (urlApiKey) { apiKey = urlApiKey; localStorage.setItem('kodall_api_key', urlApiKey) }
if (urlUser) { username = urlUser; localStorage.setItem('kodall_username', urlUser) }
if (urlPass) { password = urlPass; localStorage.setItem('kodall_password', urlPass) }
if (!urlApiKey && !urlUser && !urlPass) {
apiKey = localStorage.getItem('kodall_api_key') || ''
username = localStorage.getItem('kodall_username') || ''
password = localStorage.getItem('kodall_password') || ''
}
const isLoggedIn = localStorage.getItem('kodall_is_logged_in') === 'true'
const savedMode = localStorage.getItem('kodall_auth_mode')
if (isLoggedIn) {
const key = urlApiKey || apiKey || localStorage.getItem('kodall_api_key')
const user = urlUser || username || localStorage.getItem('kodall_username')
const pass = urlPass || password || localStorage.getItem('kodall_password')
if (savedMode === 'basic' && user && pass) {
await handleLoginBasic(user, pass)
} else if (key) {
await handleConnectApiKey(key)
}
}
} finally {
isInitializing = false
}
})
</script>
{#if isInitializing}
<p>Connecting to Kodall...</p>
{:else if !isAuthenticated}
<section class="auth-card">
<button on:click={() => handleConnectApiKey()}>Connect with API Key</button>
<button on:click={() => handleLoginBasic()}>Sign In with Password</button>
</section>
{:else}
<div class="dashboard">...</div>
{/if}
Fetch hierarchical parent-child relationships and create nested graphs in a single round-trip:
<script lang="ts">
interface TodoChild {
item_key?: number
description: string
item_completed?: number | boolean
}
interface Todo {
key: number
title: string
is_completed: number | boolean
children?: { todo_todo_item?: TodoChild[] }
}
let todos: Todo[] = []
// 1. Group flat joined rows into tree
function groupJoinedTodos(rows: any[]): Todo[] {
const map = new Map<number, Todo>()
for (const row of rows) {
if (row.key == null) continue
if (!map.has(row.key)) {
map.set(row.key, { key: row.key, title: row.title || '', is_completed: row.is_completed, children: { todo_todo_item: [] } })
}
const parent = map.get(row.key)!
if (row.item_key != null) {
parent.children!.todo_todo_item!.push({
item_key: row.item_key,
description: row.description || '',
item_completed: row.item_completed
})
}
}
return Array.from(map.values())
}
// 2. Fetch joined records
async function loadTodos(client: any) {
const result = await client.fetch(`
FETCH todo (key, title, is_completed) {
todo_item TO id_todo (key AS item_key, description, is_completed AS item_completed)
}
ORDER BY key DESC
LIMIT 100
`)
if (!isProblem(result) && Array.isArray(result)) {
todos = groupJoinedTodos(result)
}
}
// 3. Create parent entity and child items
async function handleCreateTodo(client: any, title: string, subtasks: string[]) {
const payload = {
entity_name: 'todo',
properties: { title, is_completed: 0 },
children: {
todo_todo_item: subtasks.map(desc => ({
entity_name: 'todo_item',
properties: { description: desc, is_completed: 0 }
}))
}
}
await client.create(payload)
await loadTodos(client)
}
</script>
Upload files directly using Kodall's storage API:
<script lang="ts">
async function handleUploadFile(client: any, file: File) {
const result = await client.uploadFile(file)
if (!isProblem(result) && Array.isArray(result) && result.length > 0) {
const uploaded = result[0]
console.log('Stored File ID:', uploaded.id)
}
}
</script>
Add build and deployment scripts to package.json:
{
"scripts": {
"dev": "vite dev",
"build": "vite build",
"deploy": "vite build && kodall-deploy -e dev",
"deploy:prod": "vite build && kodall-deploy -e prod"
}
}
Execute the deployment command for your target environment:
# Deploy to development environment
npm run deploy
# Or deploy to production
npm run deploy:prod
# Or run interactively
npx kodall-deploy
kodall-deploy will validate ./build, package the static assets, upload the archive to Kodall storage, update the web_app entity, and execute a live HTTP health check ping.
If your app is deployed to a subpath (e.g. "web_app_path": "/portal"), configure paths.base in svelte.config.js:
export default {
kit: {
paths: {
base: '/portal'
}
}
}
Every deployment is tracked in Kodall storage. You can inspect previous versions and instantly roll back to any prior build:
# Inspect live status across instances
npx kodall-deploy --status
# View deployment history
npx kodall-deploy --history -e prod
# Roll back to the previous deployment
npx kodall-deploy --rollback -e prod
Missing index.html in ./build@sveltejs/adapter-static was not configured with fallback: 'index.html'.svelte.config.js includes fallback: 'index.html' in the adapter options.404 on Direct Route Refresh in Productionindex.html.index.html.