自作RailsアプリをNuxt3 でSPA化してみた

# はじめに？

自作モノリスRailsアプリを Rails(バックエンド) +
Nuxt3(フロントエンド)、に分割して、SPA化
してみました。

# Easel

https://github.com/asip/easel

簡易画像ビューア。

ユーザー登録/ログイン/ログアウト機能、画像一覧機能、
画像登録(アップロード、タグ付け)機能、コメント機能
(投稿/一覧/削除)、画像プロフィール編集機能、画像検索機能、
ユーザープロフィール編集機能をもちます。

## Nuxt3フロントエンド

https://github.com/asip/easel-front

Vue.js3 のAPIは Composition API を全面採用
しています。

旧フロントエンド(Rails)のパーシャルViewを
(レイアウトや機能については)そのままVue.jsのSFCに
書き換えてUIコンポーネント化してから、変更しています。

### 利用技術

#### 利用NPMパッケージ
レイアウト： Tailwind CSS v4 + daisyUI v5
入力バリデーション: Regle
国際化(i18n): Vue i18n + Nuxt I18n
通知: Vue Sonner
カレンダー: VCalendar
ページング: vuejs-paginate-next
Googleログイン: Vue3 Google Sign-in
WYSWYGエディタ: Vue Quilly (Quill2)
画像拡大表示: GLightbox、Photoswipe
... etc

## Railsバックエンド

https://github.com/asip/easel-back

WEB API と 管理画面(Rails Admin)で構成。

Rails8.0 + PostgreSQL。

※ Rails Adminについてはカスタマイズしていません。
(RailsAdminサイドでModelのdefault_scopeの無効化は
行なっています。)

### 利用技術
DBサーバー: PostgreSQL
ストレージサーバー： MinIO (Amazon S3互換)
NoSQLキー/バリュー・ストア: Valkey (Redis互換)(Session Store用)

#### 利用Gem
ログイン認証： devise + devise-jwt
画像アップロード： Shrine
タグ： ActsAsTaggableOn
論理削除： Discard
ページング： Pagy
UIのコンポーネント化： ViewComponent
JSONシリアライザ： Alba
(簡易)管理機能：　RailsAdmin
テスト： RSpec
... etc

#### 利用NPMパッケージ
レイアウト： Tailwind CSS v4 + daisyUI v5
... etc

## 旧アプリ(Rails)

https://github.com/asip/easel-legacy

バックエンドとフロントエンドが一体化したモノリス。

Hotwire(Stimulus + Turbo)、
画像詳細ページのコメント一覧/コメント投稿機能に
Vue.js3 を採用。

#### 利用Gem
ログイン認証： devise
画像アップロード： Shrine
タグ： ActsAsTaggableOn
論理削除： Discard
ページング： Pagy
UIのコンポーネント化： ViewComponent
JSONシリアライザ： Alba
(簡易)管理機能：　RailsAdmin
... etc

#### 利用NPMパッケージ
レイアウト： Tailwind CSS v4 + daisyUI v5
... etc

## 履歴

* Rails本体: 
　5.2 → 6 → 7.0 → 7.1 -> 7.2 → 8.0 と、バージョンアップを
　経てきています。

* Railsまわり:
　アップローダの変更: Refile → Shrine
　ストレージの変更： プロジェクト内 → MinIO (AWS S3互換)
　ページングの変更： Kaminari → Pagy
　JSONシリアライザの変更： JSON:API Serialization Library → Alba
　ログイン認証の変更：Sorcery → devise

* RailsのJS/CSSまわり:
　Sprockets →
　Sprockets + Webpacker (on Node.js) → 
　Sprockets + esbuild & sass (on Node.js) →
　Propshaft + esbuild & sass (on Node.js)
　(jsbundiling-rails + cssbundling-rails)

* JSバンドラ
　Yarn → pnpm 
　(jsbundling-rails 1.3.0以降 &
 　cssbundling-rails 1.4.0以降必要)

* JSまわり
　旧Railsフロントエンド:
　Vue.js 2 → 3
　JavaScript → TypeScript
　Luminous → baguetteBox.js → GLightbox 
　VueQuill(Quill1) → Vue Quilly(Quill2)
　Vuelidate → Regle

* DBまわり
　MySQL → PostgreSQL

* NoSQLキー/バリュー・ストアまわり
　未導入 → Redis → Valkey

* CSSまわり
　Bootstrap 4 → 5 → Tailwind CSS v4 + daisyUI v5

## スクリーンショット
### 画像一覧
(未ログイン)
![resize_screencapture-localhost-3030-2025-05-20-16_12_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/85927275-7bbf-47ba-b292-d706727230e3.png)
(ログイン済み)
![resize_screencapture-localhost-3030-2025-05-20-16_27_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/8544aea0-aca4-4f6e-a8ea-2bad91e540c8.png)
### 検索条件
![resize_screencapture-localhost-3030-2025-05-20-16_15_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/cb9dc608-7a78-4baa-b028-cb26a75e9ef2.png)
### ログイン
![resize_screencapture-localhost-3030-2025-05-20-16_14_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/6f68428f-362c-4316-94de-41c902aa9d92.png)
### ユーザー登録
![resize_screencapture-localhost-3030-2025-05-20-16_14_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/b5b80aec-4dd9-4d10-96bd-0e8e46457013.png)
### ユーザープロフィール
(メールアドレス&パスワードでのログイン)
![resize_screencapture-localhost-3030-2025-05-20-16_19_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/9530079f-840b-43ad-a4ac-c054fbb7c7bb.png)
(Googleログイン)
![resize_screencapture-localhost-3030-2025-05-20-16_17_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/8283f83d-871e-4fde-a073-242da448134b.png)

### ユーザープロフィール編集
(メールアドレス&パスワードでのログイン)
![resize_screencapture-localhost-3030-2025-05-20-16_19_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/88c9add3-a817-4a82-baf1-db8c984c823c.png)
![resize_screencapture-localhost-3030-2025-05-20-16_19_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/79d2afd4-da2b-41a3-bca7-f0504c0324ac.png)
(Googleログイン)
![resize_screencapture-localhost-3030-2025-05-20-16_35_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/4321871c-b497-44a9-b791-8fa1cb258128.png)
### 画像アップロード
![resize_screencapture-localhost-3030-frames-new-2025-05-20-16_20_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/9f5b0e35-2793-4a6a-8636-6c991cf3a8b2.png)
### 画像詳細
(ログイン済み)
![resize_screencapture-localhost-3030-frames-61-2025-05-20-17_02_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/cc95b501-ed0e-4aac-a63a-892e9cb69f1f.png)

![resize_screencapture-localhost-3030-frames-61-2025-05-20-16_21_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/082254e0-9b18-481b-916d-5d41672e3bb9.png)
(未ログイン)
![resize_screencapture-localhost-3030-frames-61-2025-05-20-16_21_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/74aa5669-c71f-46ec-8810-5c4b64e6eb2d.png)
### 画像プロフィール編集
![resize_screencapture-localhost-3030-frames-61-edit-2025-05-20-16_23_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28877/9575e2ca-d968-4e6e-b495-41dc409bef96.png)


## 最後に

Nuxt3いいよ、Nuxt3！

上記アプリのSPA化に掛かった期間はちょろちょろ作成で数週間。
(初めての Nuxt3 アプリ作成、Nuxtを触るのも初めて。) 

Next.js/Remix (React)で 数週間で RailsアプリをSPA
化するのは、今のところ、自分には無理... と、
以前、書いていましたが、面倒くさがらなければ、今なら時間を
掛ければできそうです、でも、面倒くさいので、しません、
ハマりどころもありそうなので...。

コードの解説は書きません。

コードは、全てを識っている(嘘？です、書いてみただけです)。