railsとviteとvue.jsによる簡単なウェブサイトを作ってみたり

この記事はruby on railsとvue.js（特にvuetify）でウェブサイトを作る例を説明します。railsの中でvue.jsを使うには[vite_rails](https://vite-ruby.netlify.app/guide/rails.html)というgemを使います。

# 前置き

まずはこの記事を書くことになったきっかけについてです。（飛ばしても大丈夫です）

数年前からrailsによって書いたウェブサイトを更新しようとしています。そのウェブサイトはrailsの中でvue.jsを入れる為にwebpackerを使っていたのですが、webpackerはもうオワコンになっているので、更新するなら移行するべきだことになりました。webpackerの代わりの選択肢は色々ありますが、今回選択したのviteです。[vite_rails](https://vite-ruby.netlify.app/guide/rails.html)というgemでrailsと一緒にvitを使うことができます。

viteはvue.jsの作者である[尤雨溪](https://zh.wikipedia.org/wiki/尤雨溪)によって作られた開発用サーバーなので、現在vue.jsを使う時に一番おすすめの手段でもあります。勿論react.jsなどを使う場合も同様にviteを使うことができます。

ということで私も使う為にvite_railsの使い方を勉強しました。そして折角だから、勉強したことを記事に書くことにしました。

私はrailsに関する記事を書くのは今回始めてですが、ずっと前から使っていました。今回記事を書くことで私もrails関連の理解の整理ができました。

ちょっと余談ですが、丁度最近deepseekが話題になっているので、今回のvite_railsの勉強は主にdeepseekで行いました。viteをおすすめしたのもdeepseekです。今まで本を読んだりgoogleで検索したりすることで勉強してきたのですが、このようにチャットボットで勉強するのは初めてです。

この記事で使う実装コードも主にdeepseekが生成してくれたものをベースにしています。ただし私なりにだいぶ整理しました。それにdeepseekの書いたことは完璧であるわけではなく、そのまま実行してエラーになることもあります。結局聞き直したり、googleで検索したりして完成させる必要があります。それでもgoogleばかり頼る時代より勉強が速いと思います。

これは意外といい勉強し方かもしれません。deepseekがもっと早く現れたらよかったのに、と思うくらい。

こんなものが人気になっていくと、qiitaなどの記事を読む人や書く人が少なくなるのではないか……とつい思ってしまいますが、とりあえず今はそこまでではないでしょう。

余談はここまで。次は本題に入ります。


# 概要

今回作ったウェブサイトはただ新幹線駅データを閲覧したり編集したりする簡単で小さなウェブサイトです。これを選んだ理由はただ何かデータベースを使う例をしたくて、そして最近新幹線のことを勉強しているところでもあって、先月丁度この記事を書きました。

https://qiita.com/phyblas/items/3e410b14f21eeb0080d4

今回使うデータはこの記事から取ったものです。ただし全部使うのは多すぎるので、東京駅から鹿児島中央駅までの東海道山陽新幹線と九州新幹線の全部47駅だけ使うことにしました。

こうやってできたウェブサイトはこれです。

![截屏2025-02-04-23.02.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/92abfd18-25a0-3a33-d0c8-4aa379701a9d.png)

これのテーブルはvuetifyコンポネントによるもので、ページングも自動で作られています。

「編集」ボタンを押したらその駅データの編集ページに入ります。

![截屏2025-02-04-23.08.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/16a70a6a-a969-5993-53fc-32bf1a62faa3.png)

下にある「新しいデータを追加」ボタンを押したら追加ページに入ります。

![截屏2025-02-04-23.02.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/f3e25d3a-5234-c980-346b-75a0949224d3.png)

全部3ページあります。これだけです。チュートリアルらしく簡単に作れるものですね。


# 実装

## 環境

railsもvue.jsもバージョンによって大きく違う場合が多いので、今回で使ったライブラリのバージョンも記述しておきましょう。

|ruby|3.4.1|
|---|---|
|rails|8.0.1|
|vite_rails|3.0|
|vite|5.4.14|
|vite-plugin-ruby|5.1.1|
|vite-plugin-vuetify|2.1.0|
|vue|3.5.13||
|vuetify|3.7.11|
|axios|1.7.9|

## プロジェクト作成とパッケージのインストール

まずはプロジェクトの作成です。rails+vite+vue.jsなので、名前は適当に合わせて`ravivu`にします。
```sh
rails new ravivu -J -S -T -M -G -C --skip-docker --skip-kamal --skip-solid
cd ravivu
```

ここで`-J`というオプションはturbo-railsなど基本のjavascript関連の機能を入れないようにする為です。代わりにviteを使うから。

他のオプションはただ必要ないものを作らないようにする為だけです。入れなくても普通に使えます。作りたいプロジェクトや好みにもよります。詳しくは本題と関係なくてそこまで気にしなくてもいいので割愛します。railsはデフォルトで色んないいものを準備してくれて便利ですが、そこまで要らない場合も多くて逆に取り除く手間がかかりますね。

次はviteを追加します。
```sh
bundle add vite_rails
bundle exec vite install
```

そしてyarnでvue.js関連のパッケージを追加します。
```sh
yarn add vue @vitejs/plugin-vue
```

今回vuetifyも使うのでその為に必要なパッケージも追加します。
```sh
yarn add vuetify vite-plugin-vuetify @mdi/font sass-embedded
```

ウェブサイトの中でAPIとウェブページの間のデータやり取りを行う為にaxiosを使うのでこれも入れておきます。
```sh
yarn add axios
```


## 基本的な設定の調整

次は色んな設定ファイルを修正します。まずはviteの設定です。
```vite.config.ts
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import vue from '@vitejs/plugin-vue'
import vuetify from 'vite-plugin-vuetify'

export default defineConfig({
  plugins: [
    RubyPlugin(),
    vue(),
    vuetify({ autoImport: true }),
  ],
})
```

次はウェブサイトのルーティングです。今回はメインページ、データ追加ページ、個別データ編集ページ、全部3ページを作ります。又、データベースと接続為のAPIも必要です。
```config/routes.rb
Rails.application.routes.draw {
  root "home#index"
  get "add" => "home#add"
  get "edit/:id" => "home#edit"
  resources "api", only: [:create, :update, :destroy]
}
```

これで6つのルーティングができます。
|　|パス|メソッド|コントローラー|
|---|---|---|---|
|メインページ|/|get|home#index|
|データ追加ページ|/add|get|home#add|
|データ編集ページ|/edit/ {*id*}|get|home#edit|
|データ追加API|/api|post|api#create|
|データ編集API|/api/ {*id*}|patch|api#update|
|データ削除API|/api/ {*id*}|delete|api#destroy|

尚、CORS問題を防ぐ為に`config`フォルダの中の`application.rb`にこれを入れる必要があります。
```config/application.rb
config.action_controller.forgery_protection_origin_check = false
```

これを設定しないとproductionの時にaxiosを使うところにこのようなエラーが出ます。
```
[9b52a8e7-e50c-4c18-843e-f462af1086f7] HTTP Origin header (http://127.0.0.1:3000) didn't match request.base_url (https://127.0.0.1:3000)
[9b52a8e7-e50c-4c18-843e-f462af1086f7] Completed 422 Unprocessable Content in 1ms (ActiveRecord: 0.0ms (0 query, 0 cached) | GC: 0.0ms)
```

実は私は最初はこの問題にぶつかって解決する為に数時間もかけたので、特に注意して欲しいです。

これは`rack-cors`を使っても解決できません。尚、一般公開するAPIを作る場合`rack-cors`を使う必要があるはずですが、今回はウェブサイトの中でデータのやり取りをするだけなので`rack-cors`とは関係ありません。

その他に`axios.defaults.headers`の設定も必要です。これについては後述の`application.ts`に書きます。

又、これは必須ではないが、個人的おすすめ。ファイル名や変数名に`s`が勝手に追加されることを防ぐ為のおまじないです。
```config/initializers/inflections.rb
ActiveSupport::Inflector.inflections { |inflect|
  inflect.plural /^.*\S$/, '\0'
  inflect.singular /^.*\S$/, '\0'
}
```


## データベース

駅のテーブルのデータを保存するデータベースの設定です。実際にproductionの時はpostgresやmysqlを使う場合が多いが、今回は簡単な為にdevelopmentもproductionもデフォルトのsqliteを使うことにします。

まずはyamlでのの設定です。
```config/database.yml
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: storage/development.sqlite3

production:
  <<: *default
  database: storage/production.sqlite3
```

次はこのようにデータテーブルを作りたいです。

|　|列名|データ型|
|---|---|---|
|駅名|namae|text|
|読み方|yomi|text|
|都道府県|ken|text|
|開業日時|kaigyou|date|

これを実行することでその通りのテーブルを定義します。ただしidというプライマリキーと、追加と更新の日付の列も、自動的に追加されます。
```sh
rails g model eki namae yomi ken kaigyou:date
```

そうしたら`db/migrate/`フォルダの中にデータベースのテーブルを作成するrubyコードが生成されます。特に変更する必要なくこのままmigrateを実行します。

```sh
rails db:migrate
```

今回デフォルトであるsqliteを使うので、`storage`フォルダの中に`development.sqlite3`ファイルができます。

次はデータの準備です。データを入れる為の`seeds.rb`を使います。
```db/seeds.rb
ekidata = %w[
  東京,とうきょう,東京都,1964-10-01
  品川,しながわ,東京都,1964-10-01
  新横浜,しんよこはま,神奈川県,1964-10-01
  小田原,おだわら,神奈川県,1964-10-01
  熱海,あたみ,静岡県,1964-10-01
  三島,みしま,静岡県,1964-10-01
  新富士,しんふじ,静岡県,1988-03-13
  静岡,しずおか,静岡県,1964-10-01
  掛川,かけがわ,静岡県,1988-03-13
  浜松,はままつ,静岡県,1964-10-01
  豊橋,とよはし,愛知県,1964-10-01
  三河安城,みかわあんじょう,愛知県,1988-03-13
  名古屋,なごや,愛知県,1964-10-01
  岐阜羽島,ぎふはしま,岐阜県,1964-10-01
  米原,まいばら,滋賀県,1964-10-01
  京都,きょうと,京都府,1964-10-01
  新大阪,しんおおさか,大阪府,1964-10-01
  新神戸,しんこうべ,兵庫県,1972-03-15
  西明石,にしあかし,兵庫県,1972-03-15
  姫路,ひめじ,兵庫県,1972-03-15
  相生,あいおい,兵庫県,1972-03-15
  岡山,おかやま,岡山県,1972-03-15
  新倉敷,しんくらしき,岡山県,1975-03-10
  福山,ふくやま,広島県,1975-03-10
  新尾道,しんおのみち,広島県,1988-03-13
  三原,みはら,広島県,1975-03-10
  東広島,ひがしひろしま,広島県,1988-03-13
  広島,ひろしま,広島県,1975-03-10
  新岩国,しんいわくに,山口県,1975-03-10
  徳山,とくやま,山口県,1975-03-10
  新山口,しんやまぐち,山口県,1975-03-10
  厚狭,あさ,山口県,1999-03-13
  新下関,しんしものせき,山口県,1975-03-10
  小倉,こくら,福岡県,1975-03-10
  博多,はかた,福岡県,1975-03-10
  博多南,はかたみなみ,福岡県,1990-04-01
  新鳥栖,しんとす,佐賀県,2011-03-12
  久留米,くるめ,福岡県,2011-03-12
  筑後船小屋,ちくごふなごや,福岡県,2011-03-12
  新大牟田,しんおおむた,福岡県,2011-03-12
  新玉名,しんたまな,熊本県,2011-03-12
  熊本,くまもと,熊本県,2011-03-12
  新八代,しんやつしろ,熊本県,2004-03-13
  新水俣,しんみなまた,熊本県,2004-03-13
  出水,いずみ,鹿児島県,2004-03-13
  川内,せんだい,鹿児島県,2004-03-13
  鹿児島中央,かごしまちゅうおう,鹿児島県,2004-03-13
]
ekidata = ekidata.map{|eki|
  eki = eki.split(",")
  {
    namae: eki[0],
    yomi: eki[1],
    ken: eki[2],
    kaigyou: eki[3]
  }
}
Eki.create!(ekidata)
```

そしてseedの実行。
```sh
rails db:seed
```

これでデータが整いました。


## コントローラー

次はルーターで定義した通りコントローラーを作ります。
```app/controllers/api_controller.rb
class ApiController < ApplicationController
  def create
    konoeki = Eki.create(params[:ekidata].permit(:id, :namae, :yomi, :ken, :kaigyou))
    render(json: konoeki)
  end

  def update
    konoeki = Eki.find(params[:id])
    ekidata = params[:ekidata].permit(:id, :namae, :yomi, :ken, :kaigyou)
    konoeki.update(ekidata)
    render(json: konoeki)
  end

  def destroy
    konoeki = Eki.find(params[:id])
    konoeki.destroy
    render(json: konoeki[:namae]+"駅を削除完了")
  end
end
```

```app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    @taitoru = "新幹線駅一覧"
    @jsondata = Eki.all.to_json
  end

  def add
    @taitoru = "駅追加"
    @jsondata = ""
  end

  def edit
    @jsondata = Eki.find(params[:id])
    @taitoru = @jsondata.namae+"駅編集"
    @jsondata = @jsondata.to_json
  end
end
```

apiの方は応答のデータをそのままjsonとしてrenderしますが、homeはjsonを`@jsondata`という変数に入れておいてその後vueから使います。

`@taitoru`はページのtitleで、erbレイアウトの中で使われます。

## erb

ウェブサイトの各ページはvueで定義するので、erbはただレイアウトページ1つだけでいいです。
```erb:app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= @taitoru %></title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= vite_client_tag %>
    <%= vite_typescript_tag "application" %>
    <%= vite_stylesheet_tag "application.scss" %>
  </head>
  <body>
    <div id="jsondata" hidden><%= @jsondata.html_safe %></div>
    <div id="app"></div>
  </body>
</html>
```

ここで`<div id="app"></div>`はvueで形を作る部分です。

`vite_typescript_tag`はTypeScriptを使う場合であり、JavaScriptを使う場合は`vite_javascript_tag`となります。

そして各ページのerbです。ただしvueでページを作るつもりだから実は使わないが、railsのルールでは各ページに合わせるerbがないとエラーになるから、わざわざ空っぽなerbを作る必要があります。今回3あるのでこの通りに作ります。
```app/views/home/index.html.erb
```
```app/views/home/add.html.erb
```
```app/views/home/edit.html.erb
```


## entrypoint

railsプロジェクトにviteを追加した時に`app/frontend/entrypoints`の中に`application.js`は自動的に作成されているはずです（ただしもしプロジェクト作成の時に`-J`オプションを付けていなかった場合、場所は`app/javascript/entrypoints`になる）。今回TypeScriptを使うので、`application.ts`に書き換えます。
```app/frontend/entrypoints/application.ts
import { createApp } from "vue";
import { createVuetify } from "vuetify";
import { ja } from "vuetify/locale";

const vuetify = createVuetify({
  locale: {
    locale: "ja",
    messages: { ja },
  },
  theme: {
    defaultTheme: 'darkTheme',
    themes: {
      darkTheme: {
        dark: true,
        colors: {
          qiita: "#55c500",
          pixiv: "##0097f7",
          yahoo: "#f70131",
          deepseek: "#4d6bfe",
        }
      },
    },
  },
});

import indexPage from "../index-page.vue";
import addPage from "../add-page.vue";
import editPage from "../edit-page.vue";

document.addEventListener("DOMContentLoaded", () => {
  if(document.URL.includes("edit")){
    var app = createApp(editPage);
  }
  else if(document.URL.includes("add")) {
    var app = createApp(addPage);
  }
  else {
    var app = createApp(indexPage);
  }
  app.use(vuetify);
  app.mount("#app");
});

import axios from "axios";
axios.defaults.headers["X-CSRF-TOKEN"] = document.getElementsByName("csrf-token")[0].getAttribute("content");
```

ここでURLによって違うvueページが使われます。ページを分ける手段としてはvue-routeなどがありますが、今回は単純に`if`で分岐することにします。あまり簡潔でないかもしれないけど、わかりやすくて直感的だから。

`theme`は色のテーマの設定するのです。特徴を持つウェブサイトの名前を色の名前に使います。

`locale`は表示する言語の設定です。これはvuetifyの`v-data-table`などの表示に反映します。

尚、vuetifyの`v-data-table`とテーマを使う例は以前の記事に書いたことがあります。今回は実はその記事を参考にした部分が多いです。

https://qiita.com/phyblas/items/f3f40df76f85d61d5286

しかし4年も経ってvueもvuetifyも使い方が大分変わったので、その記事の使い方はほぼもう使い物にならなく、ただ参考程度にしかない状態になっていますね。

その他にスタイルシートファイルもこのように追加します。
```app/frontend/entrypoints/application.scss
@use 'vuetify/styles';
@use '@mdi/font/css/materialdesignicons.css';
```


## vue

最後にページの形を作る.vueファイルです。
メインページ。
```app/frontend/index-page.vue
<template>
  <v-app>
    <v-main>
      <v-container>
        <div class="text-h4 px-4 py-3">新幹線駅一覧</div>
        <v-data-table :items="eki_list" :headers="header_list" :items-per-page="8"
          :items-per-page-options="[8, 20, -1]">
          <template #item.namae="{ item: { namae } }">
            <a :href="'https://ja.wikipedia.org/wiki/' + namae + '駅'" target="_blank" style="font-size: 13pt;">{{ namae
              }}</a>
          </template>
          <template #item.kaigyou="{ item: { kaigyou } }">
            <v-sheet v-if="kaigyou">{{ (kaigyou as string).replace("-", "年").replace("-", "月") + "日" }}</v-sheet>
          </template>
          <template #item.henshuu="{ item: { id } }">
            <v-btn @click="henshuu(id)" color="pixiv">編集</v-btn>
          </template>
          <template #item.sakujo="{ item: { id } }">
            <v-btn @click="sakujo(id)" color="yahoo">削除</v-btn>
          </template>
        </v-data-table>
        <v-btn @click="tsuika" color="deepseek">新しいデータを追加</v-btn>
      </v-container>
    </v-main>
  </v-app>
</template>

<script lang="ts">
import axios from "axios";

export default {
  name: "indexpage",
  data: () => ({
    eki_list: [],
    header_list: [
      { value: "id", },
      { title: "駅名", value: "namae", },
      { title: "読み方", value: "yomi", sortable: true },
      { title: "都道府県", value: "ken", sortable: true },
      { title: "開業", value: "kaigyou", sortable: true },
      { value: "henshuu" },
      { value: "sakujo" },
    ],
  }),
  methods: {
    tsuika() {
      location.href = "/add";
    },
    henshuu(id: number) {
      location.href = "/edit/" + id;
    },
    sakujo(id: number) {
      if (confirm("消して宜しいの？")) {
        let url = "/api/" + id;
        axios.delete(url)
          .then((res) => {
            if (res.status == 200) {
              location.reload();
            }
          })
          .catch((err) => {
            alert(err);
          });
      }
    },
  },
  mounted() {
    this.eki_list = JSON.parse(document.getElementById("jsondata")!.textContent as string);
  },
};
</script>
```

データを追加するページ。
```app/frontend/add-page.vue
<template>
  <v-app>
    <v-main>
      <v-container>
        <v-card color="deepseek" style="width: 300px;">
          <v-card-title>
            駅データ追加
          </v-card-title>
          <v-card-text>
            <v-sheet v-for="col in Object.keys(ekidata)">
              <v-text-field v-model="ekidata[col]" :label="col" style="margin: 5px" hide-details></v-text-field>
            </v-sheet>
          </v-card-text>
        </v-card>
        <v-btn @click="tsuika" class="mx-1" color="qiita" width="160">追加</v-btn>
        <v-btn @click="modoru" class="my-1" color="yahoo" width="100">取消</v-btn>
      </v-container>
    </v-main>
  </v-app>
</template>

<script lang="ts">
import axios from "axios";

export default {
  name: "addpage",
  data: () => ({
    ekidata: { namae: "", yomi: "", ken: "", kaigyou: "" }
  }),
  methods: {
    tsuika() {
      axios.post("/api/", { ekidata: this.ekidata })
        .then((res) => {
          if (res.status == 200) {
            this.modoru();
          }
        })
        .catch((err) => {
          alert(err);
        });
    },
    modoru() {
      location.href = "/";
    },
  },
};
</script>
```

データを更新するページ。
```app/frontend/edit-page.vue
<template>
  <v-app>
    <v-main>
      <v-container>
        <v-card color="pixiv" style="width: 300px;">
          <v-card-title>
            駅データ編集
          </v-card-title>
          <v-card-text>
            <v-sheet v-for="col in Object.keys(ekidata).slice(0, 5)">
              <v-text-field v-model="ekidata[col]" :label="col" style="margin: 5px" hide-details></v-text-field>
            </v-sheet>
          </v-card-text>
        </v-card>
        <v-btn @click="hozon" class="mx-1" color="qiita" width="160">保存</v-btn>
        <v-btn @click="modoru" class="my-1" color="yahoo" width="100">取消</v-btn>
      </v-container>
    </v-main>
  </v-app>
</template>

<script lang="ts">
import axios from "axios";

export default {
  name: "editpage",
  data: () => ({
    ekidata: {},
    id: null,
  }),
  methods: {
    hozon() {
      axios.patch("/api/" + this.id, { ekidata: this.ekidata })
        .then((res) => {
          if (res.status == 200) {
            this.modoru();
          }
        })
        .catch((err) => {
          alert(err);
        });

    },
    modoru() {
      location.href = "/";
    },
  },
  mounted() {
    this.ekidata = JSON.parse(document.getElementById("jsondata")!.textContent as string);
    this.id = this.ekidata.id;
  },
};
</script>
```

尚、ページに使うデータはaxiosを使うことでAPIから読み込むという方法もできますが、今回はコントローラーから準備しておいたjsondataを使うことにしています。axiosを使うのはデータの削除/編集/追加の時だけとなっています。

これでコードの準備は整いました。


## developmentで起動

開発の時はdevelopmentモードで、2つのサーバーを同時に起動する必要があります。まずviteサーバーを起動します。
```sh
bundle exec vite dev
```

そしてrailsサーバーの起動。
```sh
rails s
```

そうしたら`http://127.0.0.1:3000/`からウェブサイトにアクセスできるようになります。


## productionに向かう

開発が終わったら次は本番に向かいます。まずデータベースの準備ですが、普段developmentとproductionでデータベースが違うので、個別で準備する必要があります。migrateとseedもproductionの環境でもう一度やる必要があります。

```sh
rails db:migrate RAILS_ENV=production
rails db:seed RAILS_ENV=production
```

そしてJavaScriptをプリコンパイルします。これでもう個別で本番ではサーバーを立てる必要がなくなります。
```sh
bundle exec vite build
```

最後にサーバーを起動します。
```sh
rails s -e production
```

以上これで完成です。

## 纏め

色んなファイルを編集したり作成したりしたので、見落とさないようにここで纏めておきます。

<table>
<tr><td>vite.config.ts</td></tr>
<tr><td>config/routes.rb</td></tr>
<tr><td>config/application.rb</td></tr>
<tr><td>config/database.yml</td></tr>
<tr><td>config/initializers/inflections.rb</td></tr>
<tr><td>db/seeds.rb</td></tr>
<tr><td>app/controllers/api_controller.rb</td></tr>
<tr><td>app/controllers/home_controller.rb</td></tr>
<tr><td>app/views/layouts/application.html.erb</td></tr>
<tr><td>app/views/home/index.html.erb</td></tr>
<tr><td>app/views/home/add.html.erb</td></tr>
<tr><td>app/views/home/edit.html.erb</td></tr>
<tr><td>app/frontend/index-page.vue</td></tr>
<tr><td>app/frontend/add-page.vue</td></tr>
<tr><td>app/frontend/edit-page.vue</td></tr>
<tr><td>app/frontend/entrypoints/application.ts</td></tr>
<tr><td>app/frontend/entrypoints/application.scss</td></tr>
</table>

# 参考

### vite_rails
- [Vite Ruby で 爆速で Rails + Vue3 + TypeScript 環境を作成する！](https://qiita.com/h-kawamoto/items/48eb32ed88219cbe744f)
- [Vue.jsにvuetifyとbootstrap-vueを共存させる](https://qiita.com/devzooiiooz/items/f5a576a4a91c5fd4c545)
- [【備忘録】Vue.js3の導入設定・正常表示確認](https://qiita.com/KM9973/items/1e9329de57bb8fafb9f3)
- [Rails のフロントエンドツールに vite_rails を使う](https://qiita.com/kato-hiroaki/items/d93845706ffbcee7b760)
- [RailsアプリにVue.jsをモノリシックに導入するための準備](https://qiita.com/KM9973/items/a237ee70edada86c4e12)
- [Rails7系にviteを使ってVue3を入れてみた](https://qiita.com/RUrushi/items/6a793bbc6699f1236c6d)
- [rails+VueでCRUDをやってみた(spa)](https://qiita.com/gabakugik/items/7ca8c39fee4002c27ccd)
- [Rails 7 + Vue.js 3 でaxiosを使ったAPI通信で遭遇したエラーとその解決](https://qiita.com/KM9973/items/79dc695d269de37f45ff)
- [railsとvue.jsを使用してchatアプリを作ってみた](https://qiita.com/narihare1130/items/7ff7f1ccc47d9e1ffcd8)

### その他
- [Rails resourcesとは(実務に向けた解説)](https://qiita.com/Hashimoto-Noriaki/items/441003dd2ec358a331b5)
- [【Vue】axiosで、デフォルトでCSRFトークンを設定できるようにする](https://qiita.com/ngron/items/2faae8068baa093d6aba)
- [Rails5系以上でInvalidAuthenticityTokenエラー](https://qiita.com/munaita_/items/e1d36fac9515654a76de)
- [【Rails API】CSRF 対策をあきらめないでちゃんとやる](https://www.midorimici.com/posts/rails-api-csrf)
- [小さく薄くrails newする(ViewやJSが必要ない場合)](https://qiita.com/shifumin/items/f4f4ea68d9963dbe9ca2)
- [いい感じにrails newする　rails newのオプション](https://qiita.com/morioka1206/items/d9297cc5d5085422acac)
- [Rails8 apiモードのskipオプション比較](https://qiita.com/devzooiiooz/items/5c724fe36a55af9ba16d)
- [rails newするときによく使うオプションと、rails newした後によく行う設定](https://qiita.com/jun_jun_jun/items/dd260c43387a8e17803d)