Nuxt 3 で多目的オンラインツール集「HamsterTools」を構築したときのメモ

## はじめに
日常のちょっとした作業をブラウザだけで完結させたい場面は意外と多い。  
HamsterTools は「画像リサイズ」「JSON ⇆ CSV 変換」「乱数生成」など、細かいユーティリティを一か所に集約したサイトだ。今回は **Nuxt 3 + TypeScript + Tailwind CSS** で実装した際に得られた知見を、備忘録としてまとめておく。実際のサービスは下記から確認できる。  

> 👉 [HamsterTools](https://www.hamstertools.org)

---

## 技術スタック
| 区分 | 採用技術 | 補足 |
| ---- | -------- | ---- |
| フレームワーク | Nuxt 3 (SSR / SPA 切替可) | v3.10.0 |
| 言語 | TypeScript | Composition API |
| UI | Tailwind CSS + daisyUI | 軽量 & コンポーネント化 |
| 画像処理 | Browser Image Compression / Pica | Web Worker 併用 |
| デプロイ | Cloudflare Pages | GitHub Actions で CI/CD |
| その他 | @vueuse/core / Pinia / vanilla-extract | - |

---

## 1. ルーティングと静的事前生成
HamsterTools では「ツール数が増え続ける」ことを前提に、以下の構成を取った。

```text
/pages
 ├─ all_tools/
 │   ├─ json_formatter/
 │   │   └─ index.vue
 │   ├─ resize_image/
 │   │   └─ index.vue
 │   └─ ...
 └─ index.vue   # トップ
```

`nuxt.config.ts` で

```ts
export default defineNuxtConfig({
  nitro: { preset: 'static' },
  routeRules: { '/**': { static: true } }
})
```

としておくと、**npm run generate** 時に各ツールページが静的 HTML として吐き出される。増えた分はコミット前に自動検出されるため、運用コストがほぼゼロで済む。

---

## 2. i18n を後付けで入れるコツ
公開後に「多言語対応してほしい」と要望が来たため、`vue-i18n` をあとから導入した。ポイントは **route prefix** ではなく **path optional** 方式を採用したこと。

```ts
const { locale } = useI18n()

const canonical = computed(() =>
  locale.value === 'en'
    ? `https://www.hamstertools.org${route.path}`
    : `https://www.hamstertools.org/${locale.value}${route.path}`
)
```

SEO 面では `hreflang` と `canonical` をきちんと出し分けるだけで十分だった。

---

## 3. 画像変換系ツールを Web Worker 化
`png → jpg` などブラウザ側で画像を扱うツールは、メインスレッドを塞ぎやすい。`vite-plugin-worker` を入れて以下のように分離したところ、FPS が大幅に改善した。

```ts
// useConvertWorker.ts
import ConverterWorker from './imageWorker?worker'

export const useConvertWorker = () => {
  const worker = new ConverterWorker()

  const convert = (file: File) =>
    new Promise<File>((resolve) => {
      worker.postMessage({ file })
      worker.onmessage = (e) => resolve(e.data)
    })

  return { convert }
}
```

---

## 4. UI を Tailwind × daisyUI で統一
デザインに工数を割かず、それでいて「ツールごとに雰囲気が違う」状態を避けるために daisyUI を利用した。カスタムテーマを 1 種類定義し、

```html
<button class="btn btn-primary">実行</button>
```

だけで色・フォーカスリングが揃うのは思った以上に楽だった。

---

## 5. Lighthouse 100 を狙う細かいチューニング
1. `nuxt telemetry disable`  
2. `nuxt-icon` で SVG-sprite 化  
3. `viteImagemin` + `squoosh` でビルド時に自動圧縮  
4. `<NuxtLink>` の `prefetch="false"` を徹底  

これでほぼ全ページが Lighthouse 100 に到達。特に画像圧縮は効果が大きかった。

---

## 6. GitHub Actions による自動デプロイ
```yaml
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run generate
      - uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          projectName: hamstertools
          directory: .output/public
```

CI で落とし穴になりやすい `node-sass` などのネイティブ依存は使わず、全て ESM のみで完結させることでビルドを安定させた。

---

## まとめ
HamsterTools 全体を **Nuxt 3 + 静的ホスティング** に寄せたことで、  

1. ページ追加が「`pages/` に置くだけ」で完了  
2. SSR ではなく静的 HTML のためランニングコストがゼロ  
3. Lighthouse 100 & 安定 60 fps  

という三拍子が揃った。今後は Service Worker まわりを強化し、オフラインでも一部ツールを動かせるようにする予定だ。

---

*この記事はサービス開発時の技術的な備忘として公開しています。内容に誤りや改善案があればコメントでお知らせください。*