0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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

👉 HamsterTools


技術スタック

区分 採用技術 補足
フレームワーク 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 では「ツール数が増え続ける」ことを前提に、以下の構成を取った。

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

nuxt.config.ts

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

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


2. i18n を後付けで入れるコツ

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

const { locale } = useI18n()

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

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


3. 画像変換系ツールを Web Worker 化

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

// 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 種類定義し、

<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 による自動デプロイ

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 まわりを強化し、オフラインでも一部ツールを動かせるようにする予定だ。


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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?