mcpField
M
migrate-lovable

migrate-lovable

github.com/stahura開発24204 回インストール26 週間前に更新
リポジトリ

migrate-lovable について

Lovableやv0で生成したアプリを、Domoのクライアント側Reactと静的ファイルの要件に合わせるためのガイドです。開発者がサーバー側コードを特定して手動で移行する手順を示し、自動変換は行いません。

主な機能

  1. 01SSRコードとAPIルートの特定
  2. 02バックエンド取得をDomo APIに置換
  3. 03ルーティングとVite設定の変更
  4. 04Lovable固有の依存関係の削除
  5. 05マニフェスト、サムネイル、公開ファイルの準備

活用シーン

  1. 01生成アプリのDomo非互換箇所の確認
  2. 02プロトタイプのDomoクライアント側構成への移植
  3. 03Domo公開用ビルド成果物の準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/stahura/domo-ai-vibe-rules --skill migrate-lovable

インストール後、Claude Code で/migrate-lovableを入力して読み込まれたか確認します。

詳細

Lovable から Domo への移行

生成されたアプリ(Lovable/v0)を Domo に変換する

多くの開発者は、Lovable、v0、その他同様の LLM ベースの生成ツールなどの AI ツールを使って、アプリのプロトタイプを作成しています。しかし、これらのツールは通常、Domo のクライアントサイドのみのアーキテクチャと互換性のないサーバーサイドレンダリング(SSR)を使用するアプリを生成します。

変換における課題

生成されたアプリには、次のようなものが含まれていることがよくあります。

  • サーバーサイドレンダリング(Next.js、Remix、SvelteKit、Nuxt)
  • API ルート(pages/api/ または app/api/ ディレクトリ)
  • サーバーサイドでのデータ取得(getServerSideProps、ローダーなど)
  • フレームワーク固有のルーティング(Next.js App Router、Remix routes)

Domo アプリは、次の要件を満たす必要があります。

  • 純粋なクライアントサイドの React — サーバーでのレンダリングは行いません
  • 静的ファイルベース — Domo は静的ファイルのみを提供します
  • Domo API との統合 — バックエンドエンドポイントの代わりに、domo.get()、Query、AppDBClient などを使用します
推奨される変換パターン

DA CLI は変換ツールではありません。アプリを自動的に変換することはできません。ただし、参照用の構成や出発点として利用できます。

ステップ 1:参照用の構成を生成する
# 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
ステップ 2:SSR コードを検出して削除する

SSR の兆候を確認します。

  • getServerSideProps、getStaticProps(Next.js)
  • loader、action 関数(Remix)
  • +page.server.js、+server.js(SvelteKit)
  • pages/api/ または app/api/ ディレクトリ
  • サーバーサイドでの process.env の使用
  • コンポーネント内のデータベース接続

対応: サーバーサイドのコードをすべて削除します。Domo アプリは完全にブラウザー上で動作します。

ステップ 3:データ取得を置き換える

変更前(Next.js の例):

// ❌ 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);
}

変更後(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();
ステップ 4:ルーティングを更新する

変更前(Next.js):

// ❌ Next.js routing
import Link from 'next/link';
<Link href="/dashboard">Dashboard</Link>

変更後(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');
ステップ 5:ビルド設定を修正する

変更前(Next.js):

// next.config.js
module.exports = {
  // Next.js config
}

変更後(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()],
});

lovable-tagger などの Lovable 固有の Vite プラグインを削除します。

// ❌ Remove this
import { componentTagger } from "lovable-tagger";
plugins: [react(), mode === "development" && componentTagger()].filter(Boolean),

// ✅ Replace with
plugins: [react()],
ステップ 6:Lovable 固有の依存関係を削除する

Lovable/v0 アプリには、削除が必要なツールが含まれています。

削除する依存関係:

  • next-themes — SSR のテーマプロバイダーです。useTheme() を、ハードコードしたテーマ文字列(例:"light")に置き換えます
  • lovable-tagger — Lovable の開発時専用コンポーネントタグ付けツールです

削除するファイル:

  • playwright.config.ts — lovable-agent-playwright-config を使用します
  • playwright-fixture.ts — Lovable のテスト用フィクスチャを再エクスポートします
  • bun.lock / bun.lockb — Lovable は Bun を使用します。npm に統一してください
  • App.css — 生成されたアプリでは使用されていないことがよくあります(先に確認してください)

Sonner トースターでの next-themes の使用を修正する:

// ❌ 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" />
ステップ 7:Domo マニフェストを作成する

Domo では、公開ディレクトリに manifest.json が必要です。

{
  "name": "My App Name",
  "version": "1.0.0",
  "size": {
    "width": 10,
    "height": 10
  },
  "mapping": [],
  "fileName": "index.html",
  "id": "",
  "proxyId": ""
}
  • name — Domo での表示名
  • size — カードのサイズ(Domo ダッシュボードのグリッド上の列数 x 行数)
  • mapping — データセットのマッピング(モックデータまたは AppDB を使用する場合は空の配列)
  • fileName — エントリー HTML ファイル(常に index.html)
  • id — 空のままにします。初回の公開時に domo publish が値を設定します
ステップ 8:サムネイルを作成する

必須 — これがないと domo publish は失敗します。

プロジェクトのルートに、名前を thumbnail.png とした 300x300 PNG を配置します。これは Domo Appstore とモバイルアプリで使用されます。

# 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')
"

または、任意の 300x300 PNG 画像を使用します。

ステップ 9:dist/ ディレクトリから公開する

重要 — プロジェクトのルートから domo publish を実行すると、CLI が node_modules、ソースファイル、設定ファイルをアップロードしようとして、ペイロードが大きくなりすぎるため、400: Unable to parse form content エラーが発生します。

解決策: manifest.json と thumbnail.png を dist/ にコピーして、そこから公開します。

# Manual publish
npm run build
cp manifest.json thumbnail.png dist/
cd dist && domo publish

package.json のスクリプトを更新して自動化します。

{
  "scripts": {
    "build": "vite build && cp manifest.json thumbnail.png dist/",
    "publish": "npm run build && cd dist && domo publish"
  }
}

これにより、ビルド済みアセット、マニフェスト、サムネイルのみがアップロードされます。

ステップ 10:コンポーネントを移植する

DA CLI を使用して適切な構成の新しいコンポーネントを生成してから、ロジックを移植します。

# Generate component structure
da generate component SalesChart

# Copy component logic from generated app
# Replace data fetching with Domo APIs
# Update imports and dependencies
DA CLIでできること

✅ 参照用の構成 - Domo アプリの正しい構成を示します
✅ コンポーネントの生成 - 適切な構成のコンポーネントを作成します
✅ パターンの例 - Domo の規約を紹介します
✅ 新規作成 - 変換が複雑すぎる場合は、新しく作成してロジックを移植できます

DA CLIでできないこと

❌ 自動変換 - SSR をクライアントサイドに変換しません
❌ 移行ウィザード - ステップごとに変換するツールはありません
❌ コード変換 - フレームワーク固有のコードを書き換えません
❌ API の移行 - バックエンドへの呼び出しを自動的に置き換えません

変換の完全チェックリスト
  • すべての SSR コードを検出して削除する
  • API ルート(pages/api/、app/api/)を削除する
  • バックエンドへの fetch() 呼び出しを Domo API に置き換える
  • ルーティングを BrowserRouter から HashRouter に更新する
  • Vite の base: './' を設定する
  • Vite の設定から Lovable 固有のプラグイン(lovable-tagger)を削除する
  • next-themes を削除し、useTheme() をハードコードされたテーマに置き換える
  • Lovable のファイル(playwright.config.ts、playwright-fixture.ts、bun.lock*)を削除する
  • Domo API との統合用に ryuu.js 依存関係を追加する
  • 環境変数を置き換える(代わりに Domo API を使用する)
  • インポートを更新する(Next.js/Remix/Lovable 固有のものを削除する)
  • アプリ名、バージョン、サイズを含む manifest.json を作成する
  • プロジェクトルートに thumbnail.png(300x300 PNG)を作成する
  • ビルドスクリプトを更新し、manifest.json と thumbnail.png を dist/ にコピーする
  • すべてのデータ取得が Domo API で動作することをテストする
  • アプリがローカルでビルドおよび実行されることを確認する(npm run build)
  • dist/ ディレクトリから公開する(cd dist && domo publish)
例:シンプルなダッシュボードの変換

変換前(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>;
}

変換後(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>;
}

コメント