skills/custom-apps/migrate-lovable/SKILL.md
Convert SSR-heavy Lovable/v0 apps into client-only Domo apps.
npx skillsauth add stahura/domo-ai-vibe-rules migrate-lovableInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Many developers use AI tools like Lovable, v0, or similar LLM-based generators to create app prototypes. However, these tools typically generate apps with server-side rendering (SSR) that are incompatible with Domo's client-side-only architecture.
Generated apps often include:
pages/api/ or app/api/ directories)getServerSideProps, loaders, etc.)Domo apps must be:
domo.get(), Query, AppDBClient, etc. instead of backend endpointsDA CLI is NOT a conversion tool - it doesn't automatically convert apps. However, you can use it as a reference structure and starting point.
# Create a fresh Domo app to use as reference
da new my-converted-app
cd my-converted-app
# This gives you the correct structure to compare against
Check for SSR indicators:
getServerSideProps, getStaticProps (Next.js)loader, action functions (Remix)+page.server.js, +server.js (SvelteKit)pages/api/ or app/api/ directoriesprocess.env usageAction: Remove all server-side code. Domo apps run entirely in the browser.
Before (Next.js example):
// ❌ Server-side data fetching
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
// ❌ API route
// pages/api/users.ts
export default async function handler(req, res) {
const users = await db.users.findMany();
res.json(users);
}
After (Domo):
// ✅ Client-side with Domo APIs
import domo from 'ryuu.js';
import Query from '@domoinc/query';
import { AppDBClient } from '@domoinc/toolkit';
// Fetch from Domo dataset
const data = await domo.get('/data/v1/sales');
// Or use Query API for filtered/aggregated data
const summary = await new Query()
.select(['region', 'sales'])
.groupBy('region', { sales: 'sum' })
.fetch('sales-dataset');
// Or use AppDB for document storage
const tasksClient = new AppDBClient.DocumentsClient('Tasks');
const tasks = await tasksClient.get();
Before (Next.js):
// ❌ Next.js routing
import Link from 'next/link';
<Link href="/dashboard">Dashboard</Link>
After (Domo):
// ✅ HashRouter for client-side routing
import { HashRouter, Routes, Route } from 'react-router-dom';
import domo from 'ryuu.js';
// Use HashRouter (works without server rewrites)
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</HashRouter>
// For Domo navigation, use domo.navigate()
domo.navigate('/page/123456789');
Before (Next.js):
// next.config.js
module.exports = {
// Next.js config
}
After (Domo - Vite):
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
base: './', // CRITICAL: Relative paths for Domo
plugins: [react()],
});
Remove Lovable-specific Vite plugins like lovable-tagger:
// ❌ Remove this
import { componentTagger } from "lovable-tagger";
plugins: [react(), mode === "development" && componentTagger()].filter(Boolean),
// ✅ Replace with
plugins: [react()],
Lovable/v0 apps include tooling that must be removed:
Dependencies to remove:
next-themes — SSR theme provider; replace useTheme() with a hardcoded theme string (e.g. "light")lovable-tagger — Lovable's dev-only component taggerFiles to delete:
playwright.config.ts — Uses lovable-agent-playwright-configplaywright-fixture.ts — Re-exports Lovable test fixturesbun.lock / bun.lockb — Lovable uses Bun; standardize on npmApp.css — Often unused in generated apps (verify first)Fix next-themes usage in Sonner toaster:
// ❌ Before (depends on next-themes)
import { useTheme } from "next-themes";
const { theme = "system" } = useTheme();
<Sonner theme={theme as ToasterProps["theme"]} />
// ✅ After (hardcoded, no SSR dependency)
<Sonner theme="light" />
Domo requires a manifest.json in the publish directory:
{
"name": "My App Name",
"version": "1.0.0",
"size": {
"width": 10,
"height": 10
},
"mapping": [],
"fileName": "index.html",
"id": "",
"proxyId": ""
}
name — Display name in Domosize — Card dimensions (columns x rows on the Domo dashboard grid)mapping — Dataset mappings (empty array if using mock data or AppDB)fileName — Entry HTML file (always index.html)id — Leave empty; domo publish fills this on first publishREQUIRED — domo publish will fail without this.
Place a 300x300 PNG named thumbnail.png in the project root. This is used in the Domo Appstore and mobile app.
# Generate a simple thumbnail programmatically (Python + Pillow)
python3 -c "
from PIL import Image, ImageDraw, ImageFont
img = Image.new('RGB', (300, 300), color=(15, 23, 42))
draw = ImageDraw.Draw(img)
draw.rectangle([0, 0, 300, 6], fill=(99, 102, 241))
try:
font = ImageFont.truetype('/System/Library/Fonts/Helvetica.ttc', 72)
except: font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), 'EF', font=font)
draw.text(((300-(bbox[2]-bbox[0]))/2, 110), 'EF', fill='white', font=font)
img.save('thumbnail.png')
"
Or use any 300x300 PNG image.
dist/ DirectoryCRITICAL — Running domo publish from the project root causes a 400: Unable to parse form content error because the CLI tries to upload node_modules, source files, and config files, creating an oversized payload.
Solution: Copy manifest.json and thumbnail.png into dist/, then publish from there:
# Manual publish
npm run build
cp manifest.json thumbnail.png dist/
cd dist && domo publish
Automate it by updating package.json scripts:
{
"scripts": {
"build": "vite build && cp manifest.json thumbnail.png dist/",
"publish": "npm run build && cd dist && domo publish"
}
}
This ensures only built assets, the manifest, and thumbnail are uploaded.
Use DA CLI to generate new components with correct structure, then port logic:
# Generate component structure
da generate component SalesChart
# Copy component logic from generated app
# Replace data fetching with Domo APIs
# Update imports and dependencies
✅ Reference structure - Shows correct Domo app organization
✅ Component generation - Creates properly structured components
✅ Pattern examples - Demonstrates Domo conventions
✅ Starting fresh - If conversion is too complex, start new and port logic
❌ No automatic conversion - Doesn't transform SSR to client-side
❌ No migration wizard - No step-by-step conversion tool
❌ No code transformation - Doesn't rewrite framework-specific code
❌ No API migration - Doesn't replace backend calls automatically
pages/api/, app/api/)fetch() calls to backend with Domo APIsBrowserRouter to HashRouterbase: './'lovable-tagger) from Vite confignext-themes and replace useTheme() with hardcoded themeplaywright.config.ts, playwright-fixture.ts, bun.lock*)ryuu.js dependency for Domo API integrationmanifest.json with app name, version, and sizethumbnail.png (300x300 PNG) in project rootmanifest.json + thumbnail.png into dist/npm run build)dist/ directory (cd dist && domo publish)Original (Lovable/Next.js):
// pages/dashboard.tsx
export async function getServerSideProps() {
const sales = await fetch('http://api.company.com/sales').then(r => r.json());
return { props: { sales } };
}
export default function Dashboard({ sales }) {
return <div>{/* Render sales data */}</div>;
}
Converted (Domo):
// src/components/Dashboard/Dashboard.tsx
import { useEffect, useState } from 'react';
import domo from 'ryuu.js';
export default function Dashboard() {
const [sales, setSales] = useState([]);
useEffect(() => {
// Fetch from Domo dataset instead of backend
domo.get('/data/v1/sales').then(setSales);
}, []);
return <div>{/* Render sales data */}</div>;
}
tools
Step-by-step orchestrator for building Domo App Studio apps with native KPI cards via community-domo-cli. Sequences app creation, pages, theme, hero metrics, native charts, filter cards, layout assembly, and navigation. CLI-first — no raw API calls.
tools
Create, update, and execute Magic ETL dataflows programmatically via API and CLI. Covers DAG-based JSON dataflow definitions, input/transform/output node wiring, join operations, and execution lifecycle.
tools
Magic ETL dataflows via community-domo-cli — list, get-definition, create, update, run, execution status; JSON DAG actions, transforms, joins. Use when automating dataflows with the community Domo CLI end-to-end. For REST/Java-CLI–first flows or mixed API patterns, use magic-etl instead.
development
Clean, professional dashboard theme for Domo custom apps. CSS custom properties, layout patterns, typography, and design polish that feel native to the Domo platform. Includes OKLCH color palette, layered shadows, concentric border radius, tabular numbers, and micro-interaction patterns.