未分類
Eurekapu 移行計画: Nuxt 2 → Nuxt 3 + Vuetify 3
概要
| 項目 | 現在 | 移行先 |
|---|---|---|
| フレームワーク | Nuxt 2.15.8 | Nuxt 3 |
| UIライブラリ | Vuetify 1.x | Vuetify 3 |
| Vue | Vue 2 (Options API) | Vue 3 (Composition API) |
| 状態管理 | Vuex | Pinia |
| Vueファイル数 | 1052 | - |
移行戦略
段階的移行 - 両プロジェクトを並行運用しながらセクションごとに移行
Phase 0: 準備(基盤構築)
0-1. 新規リポジトリ作成
cd C:\Users\numbe\Git_repo
npx nuxi@latest init eurekapu-nuxt3
cd eurekapu-nuxt3
pnpm install
0-2. Vuetify 3 インストール
pnpm add -D vuetify vite-plugin-vuetify
pnpm add @mdi/font
0-3. nuxt.config.ts 設定
// nuxt.config.ts
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
export default defineNuxtConfig({
build: {
transpile: ['vuetify'],
},
modules: [
(_options, nuxt) => {
nuxt.hooks.hook('vite:extendConfig', (config) => {
// @ts-expect-error
config.plugins.push(vuetify({ autoImport: true }))
})
},
],
vite: {
vue: {
template: {
transformAssetUrls,
},
},
},
})
0-4. Vuetify プラグイン作成
// plugins/vuetify.ts
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
export default defineNuxtPlugin((app) => {
const vuetify = createVuetify({
theme: {
defaultTheme: 'light',
},
})
app.vueApp.use(vuetify)
})
0-5. Pinia インストール
pnpm add pinia @pinia/nuxt
// nuxt.config.ts に追加
modules: ['@pinia/nuxt'],
Phase 1: コアコンポーネント移行
対象ファイル(約20ファイル)
layouts/default.vuecomponents/BaseComponents/*.vuecomponents/Menu.vuecomponents/List.vuecomponents/Breadcrumbs.vue
変換パターン
グリッドシステム
<!-- 旧 Vuetify 2 -->
<v-layout row class="justify-center">
<v-flex px-3 pt-1 xs12 sm6 md4>
内容
</v-flex>
</v-layout>
<!-- 新 Vuetify 3 -->
<v-row class="justify-center">
<v-col cols="12" sm="6" md="4" class="px-3 pt-1">
内容
</v-col>
</v-row>
カードコンポーネント
<!-- 旧 Vuetify 2 -->
<v-card outlined flat>
<!-- 新 Vuetify 3 -->
<v-card variant="outlined" flat>
ルーターリンク
<!-- 旧 Nuxt 2 -->
<nuxt-link :to="path">
<!-- 新 Nuxt 3 -->
<NuxtLink :to="path">
ページスロット
<!-- 旧 layouts/default.vue -->
<nuxt />
<!-- 新 layouts/default.vue -->
<slot />
Phase 2: 状態管理
Vuex → Pinia 変換
// 旧: store/index.js (Vuex)
export const state = () => ({
user: null,
isLoggedIn: false,
})
export const getters = {
isAuthenticated: (state) => state.user,
isLoggedIn: (state) => state.isLoggedIn,
user: (state) => state.user,
}
export const mutations = {
SET_USER(state, { user }) {
state.user = user
state.isLoggedIn = true
},
}
export const actions = {
setUser({ commit }, payload) {
commit('SET_USER', payload)
},
}
// 新: stores/auth.ts (Pinia)
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
const isLoggedIn = computed(() => !!user.value)
function setUser(newUser: User) {
user.value = newUser
}
function logout() {
user.value = null
}
return {
user,
isLoggedIn,
setUser,
logout,
}
})
Phase 3: ページ移行(セクション別)
移行優先順位
| 優先度 | セクション | ファイル数 | 備考 |
|---|---|---|---|
| 1 | pages/lessons/financial-statements/ | 42 | メイン教材 |
| 2 | pages/lessons/intro-to-accounting/ | ~50 | 基礎コンテンツ |
| 3 | pages/lessons/excel/ | ~60 | 独立性高い |
| 4 | pages/lessons/excel-shortcuts/ | ~40 | 独立性高い |
| 5 | pages/lessons/boki3/ | ~300 | 大規模 |
| 6 | pages/lessons/bookkeeping/ | ~100 | - |
| 7 | pages/lessons/consolidated/ | ~50 | - |
| 8 | その他 | 残り | 順次対応 |
ページ変換例
<!-- 旧: pages/lessons/financial-statements/no0-introduction.vue -->
<template>
<div>
<v-layout row class="justify-center">
<v-flex px-3 pt-1 xs12 sm12 md10 lg7>
<h1>タイトル</h1>
<nuxt-link :to="items[2].children[0].url">
リンク
</nuxt-link>
</v-flex>
</v-layout>
</div>
</template>
<script>
export default {
data() {
return {
items: [],
}
},
computed: {
someValue() {
return this.items.length
},
},
methods: {
handleClick() {
// ...
},
},
}
</script>
<!-- 新: pages/lessons/financial-statements/no0-introduction.vue -->
<template>
<div>
<v-row class="justify-center">
<v-col cols="12" md="10" lg="7" class="px-3 pt-1">
<h1>タイトル</h1>
<NuxtLink :to="items[2].children[0].url">
リンク
</NuxtLink>
</v-col>
</v-row>
</div>
</template>
<script setup lang="ts">
const items = ref([])
const someValue = computed(() => items.value.length)
function handleClick() {
// ...
}
</script>
自動変換スクリプト
scripts/migrate-vuetify.mjs
import fs from 'fs'
import path from 'path'
import { glob } from 'glob'
const replacements = [
// グリッドシステム
[/<v-layout\s+row/g, '<v-row'],
[/<\/v-layout>/g, '</v-row>'],
[/<v-flex\s+([^>]*?)xs(\d+)/g, '<v-col $1cols="$2"'],
[/<v-flex\s+([^>]*?)sm(\d+)/g, '<v-col $1sm="$2"'],
[/<v-flex\s+([^>]*?)md(\d+)/g, '<v-col $1md="$2"'],
[/<v-flex\s+([^>]*?)lg(\d+)/g, '<v-col $1lg="$2"'],
[/<v-flex/g, '<v-col'],
[/<\/v-flex>/g, '</v-col>'],
// カード
[/\soutlined(?=[\s>])/g, ' variant="outlined"'],
[/\sflat(?=[\s>])/g, ' flat'],
// ルーター
[/<nuxt-link/g, '<NuxtLink'],
[/<\/nuxt-link>/g, '</NuxtLink>'],
// レイアウト
[/<nuxt\s*\/>/g, '<slot />'],
]
async function migrateFile(filePath) {
let content = fs.readFileSync(filePath, 'utf-8')
let modified = false
for (const [pattern, replacement] of replacements) {
if (pattern.test(content)) {
content = content.replace(pattern, replacement)
modified = true
}
}
if (modified) {
fs.writeFileSync(filePath, content, 'utf-8')
console.log(`Migrated: ${filePath}`)
}
}
async function main() {
const files = await glob('**/*.vue', { ignore: 'node_modules/**' })
for (const file of files) {
await migrateFile(file)
}
}
main()
使用方法
cd eurekapu-nuxt3
node scripts/migrate-vuetify.mjs
チェックリスト
Phase 0: 準備
- 新規リポジトリ作成
- Nuxt 3 初期設定
- Vuetify 3 インストール・設定
- Pinia インストール・設定
- 基本レイアウト動作確認
Phase 1: コアコンポーネント
- layouts/default.vue 移行
- components/Menu.vue 移行
- components/List.vue 移行
- components/Breadcrumbs.vue 移行
- BaseComponents/*.vue 移行
Phase 2: 状態管理
- Pinia store 作成
- persistedstate 移行
Phase 3: ページ移行
- financial-statements/ (42ファイル)
- intro-to-accounting/
- excel/
- excel-shortcuts/
- boki3/
- その他
参考リンク
注意事項
- 並行運用期間: 移行中は両プロジェクトを並行運用し、セクション単位でリダイレクト設定
- テスト: 各フェーズ完了後に手動テスト必須
- バックアップ: 移行前に必ずgit commitでスナップショット作成
- 段階的リリース: 完成したセクションから順次本番反映
関連情報
元プロジェクト
- リポジトリ:
C:\Users\numbe\Git_repo\cockpit-nuxt-vuetify - 本番URL: https://app.eurekapu.com
プロジェクト統計
- Vueファイル数: 1052
- ページ数: ~500
- コンポーネント数: ~100
- データファイル: 35個 (JSON/JS)