Kodall
Web App Deployment

Nuxt 3/4

Step-by-step guide to building, proxying, and deploying Nuxt 3/4 applications to a Kodall instance with @kodall/kodall-deploy.

This guide walks through configuring a Nuxt 3/4 application for static Single Page Application (SPA) generation, setting up local development proxying with Nitro, integrating the TypeScript SDK (@kodall/kodall-client), and deploying the build to a Kodall instance using @kodall/kodall-deploy.

This guide demonstrates a reference integration pattern for Nuxt. Kodall natively hosts any frontend application that compiles to static web assets (HTML, JavaScript, CSS, WebAssembly).

Nuxt 4 Live Demo

Shared Demo Mode

Deployed on Kodall instance at /nuxt

Want an isolated database for this demo?

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.

https://docs-demo.kodall.io/nuxt

Installation

Install @kodall/kodall-client and @kodall/kodall-deploy in your Nuxt project:

BASH
pnpm add @kodall/kodall-client
pnpm add -D @kodall/kodall-deploy

Configuration

Development Proxy

Set ssr: false to enable static Single Page Application (SPA) generation. Configure nitro.devProxy using @kodall/kodall-deploy's built-in getNitroProxy() helper to automatically resolve your instance URL from kodall-webapp.config.json:

nuxt.config.ts
import { kodallProxyNuxt, getNitroProxy } from '@kodall/kodall-deploy'

export default defineNuxtConfig({
  // Disable server-side rendering for static SPA output
  ssr: false,

  // Option A: 1-Line Nuxt Module (Recommended)
  modules: [
    kodallProxyNuxt() // Automatically resolves instance from default_env
  ]

  // Option B: Nitro devProxy Helper
  // nitro: {
  //   devProxy: getNitroProxy()
  // }

  // Option C: Manual Nitro Proxy (Without Helper Package)
  // nitro: {
  //   devProxy: {
  //     '/auth': { target: 'https://dev.kodall.yourcompany.com/auth', changeOrigin: true, secure: true },
  //     '/rest': { target: 'https://dev.kodall.yourcompany.com/rest', changeOrigin: true, secure: true },
  //     '/storage': { target: 'https://dev.kodall.yourcompany.com/storage', changeOrigin: true, secure: true }
  //   }
  // }
})
kodallProxyNuxt() automatically reads the target instance URL and custom proxy_paths from default_env in kodall-webapp.config.json. To target a specific environment, pass { env: 'staging' } or explore the Local Dev Proxy Suite.

Client Setup

Create a Nuxt composable to provide a shared, reactive instance of KodallClient:

composables/useKodall.ts
import { KodallClient } from '@kodall/kodall-client'

export const useKodall = () => {
  return useState<KodallClient>('kodall-client', () => new KodallClient())
}
Because the app runs on the same origin in production and is proxied during local development, new KodallClient() requires no hardcoded baseUrl or credentials.

Deployment Configuration (kodall-webapp.config.json)

Create kodall-webapp.config.json in the root of your project. Nuxt's nuxt generate emits static files into ./.output/public:

kodall-webapp.config.json
{
  "web_app_name": "Nuxt Portal",
  "web_app_path": "/",
  "dist_path": "./.output/public",
  "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"
    }
  }
}
Instance Configuration: Do not use https://docs-demo.kodall.io. Enter the base URL of your own Kodall instance deployed from the Kodall Cloud Console.

Usage

Authentication & Session Restore

Support both API Key (stateless bearer token) and Username/Password (cookie session), restoring the session on page refresh without UI flicker:

app.vue
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { KodallClient, isProblem } from '@kodall/kodall-client'

const isInitializing = ref(true)
const isAuthenticated = ref(false)
const credentials = reactive({ apiKey: '', username: '', password: '' })

function getClient(customKey?: string): KodallClient {
  return new KodallClient({ apiKey: customKey || credentials.apiKey || undefined })
}

// Connect with API Key
async function handleConnectApiKey(keyInput?: string) {
  const key = keyInput || credentials.apiKey
  if (!key.trim()) return
  const client = getClient(key)
  const result = await client.fetch('FETCH todo (key) LIMIT 1')
  if (!isProblem(result)) {
    isAuthenticated.value = true
    localStorage.setItem('kodall_is_logged_in', 'true')
    localStorage.setItem('kodall_auth_mode', 'apikey')
    localStorage.setItem('kodall_api_key', key)
  }
}

// Login with Username & Password
async function handleLoginBasic(uInput?: string, pInput?: string) {
  const u = uInput || credentials.username
  const p = pInput || credentials.password
  if (!u || !p) return
  const client = getClient('')
  const result = await client.auth({ user: u, password: p }, { roles: true })
  if (!isProblem(result)) {
    isAuthenticated.value = true
    localStorage.setItem('kodall_is_logged_in', 'true')
    localStorage.setItem('kodall_auth_mode', 'basic')
  }
}

// Restore session on mount (anti-flicker loading splash)
onMounted(async () => {
  try {
    const params = new URLSearchParams(window.location.search)
    const urlApiKey = params.get('apiKey')
    if (urlApiKey) credentials.apiKey = urlApiKey

    if (localStorage.getItem('kodall_is_logged_in') === 'true') {
      const mode = localStorage.getItem('kodall_auth_mode')
      if (mode === 'basic') await handleLoginBasic()
      else await handleConnectApiKey()
    }
  } finally {
    isInitializing.value = false
  }
})
</script>

Parent-Child Entity Operations

Fetch hierarchical parent-child relationships in a single FETCH query (e.g. Master todo with joined todo_item child subtasks) and create master-detail graphs:

TYPESCRIPT
// 1. Fetch joined parent-child rows
const result = await client.fetch<any>(`
  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
`)

// 2. Group flat rows into structured tree objects
function groupJoinedTodos(rows: any[]) {
  const map = new Map<number, any>()
  for (const row of rows) {
    if (!map.has(row.key)) {
      map.set(row.key, {
        key: row.key,
        title: row.title,
        is_completed: row.is_completed,
        children: { todo_todo_item: [] }
      })
    }
    if (row.item_key != null) {
      map.get(row.key).children.todo_todo_item.push({
        key: row.item_key,
        description: row.description,
        is_completed: row.item_completed
      })
    }
  }
  return Array.from(map.values())
}

// 3. Create parent entity and child items atomically
await client.create({
  entity_name: 'todo',
  properties: {
    title: 'Launch Web Application',
    is_completed: 0
  },
  children: {
    todo_todo_item: [
      { entity_name: 'todo_item', properties: { description: 'Configure proxy', is_completed: 1 } },
      { entity_name: 'todo_item', properties: { description: 'Deploy application', is_completed: 0 } }
    ]
  }
})

Storage API & File Uploads

Upload and download files directly using Kodall's storage endpoints:

TYPESCRIPT
// Upload a file (Blob or File instance from <input type="file">)
const response = await fetch('/storage', {
  method: 'POST',
  headers: {
    'Content-Type': selectedFile.type || 'application/octet-stream',
    'X-File-Name': encodeURIComponent(selectedFile.name)
  },
  body: selectedFile
})

const storageRecord = await response.json()
console.log('Stored File ID:', storageRecord.id)

Deployment

Package Scripts

Add static generation and deployment scripts to package.json:

package.json
{
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "deploy": "nuxt generate && kodall-deploy -e dev",
    "deploy:prod": "nuxt generate && kodall-deploy -e prod"
  }
}

Executing Deployment

Execute the deployment command for your target environment:

BASH
# Deploy to development environment
pnpm deploy

# Or deploy to production
pnpm run deploy:prod

# Or run interactively
npx kodall-deploy

kodall-deploy will validate ./.output/public, package the static assets, upload the archive to Kodall storage, update the web_app entity, and run a live HTTP health check ping.


Advanced Configuration

Subpath Mounting

If your app is deployed to a subpath (e.g. "web_app_path": "/portal"), configure Nuxt's app.baseURL:

nuxt.config.ts
export default defineNuxtConfig({
  ssr: false,
  app: {
    baseURL: '/portal/'
  }
})

Deployment History & Rollback

Every deployment is tracked in Kodall storage. You can inspect previous versions and instantly roll back to any prior build without rebuilding:

BASH
# 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

Troubleshooting

Missing index.html in .output/public

  • Cause: nuxt generate was not run before deployment.
  • Solution: Ensure your deploy script executes nuxt generate (or run npx nuxi generate) before kodall-deploy.

CORS Errors During Local Development

  • Cause: Requests are hitting the backend directly without passing through the Nitro proxy.
  • Solution: Ensure your API requests use relative paths (/auth, /rest, /storage) and verify nitro.devProxy in nuxt.config.ts.