1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ViteとVue RouterでSPAを作ってみた

Posted at

シングルページアプリケーションを制作する要件がありました。
色々検討した結果、今回はViteVue Routerでこれを実現することにしました。

要件

  • シンプルなSPA
    今回は最初から最後までプレゼンテーションのように全画面でページ遷移するものです。
    ショッピングサイトのような、ページの一部だけ遷移させるSPAではありません。

使用するもの

制作手順

Vite導入

大体公式サイトの手順通りです。→はじめに|Vite

Viteインストール
❯ nodenv glocal 18.16.0
❯ mkdir pjname

❯ npm create vite@latest
✔ Project name: … pjname
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript

Node.jsはstableを入れました(本当は18.17.0だったけど、nodeenvにはなかったので18.16.0を)。

PJ作成時にフレームワークや言語を選びます。
今回はVueJavaScriptにしています。
(TSにしようか迷ったけど、vueも初めてなので使い慣れている方にしました…)

はい、もうこれだけでViteが動きます。
素晴らしいですよね。ありがたいですよね!!

Vite起動
cd pjname
❯ npm run dev

Vue Router導入

Viteの基盤ができたら、そこにVue Router君を追加します。

これも大体公式の手順通りです。

❯ npm install vue-router

router.jsの作成

router.jsを作成して、そこにVue Routerの設定を書いていきます。

router.js
// Vue Routerパッケージのインポート
import { createRouter, createWebHistory } from 'vue-router'

const routes = []

// 一般的なルートの追加(動的インポート)
routes.push(
  {
    path: '/test',
    name: 'test',
    component: () => import('Test.vue'),
  }
)

// 存在しないパスは全てTOPへ
routes.push(
  {
    path: '/:catchAll(.*)',
    redirect: '/'
  }
)

const router = createRouter({
  history: createWebHistory(),
  routes,
})

router.beforeEach((to, from) => {
  // URL直接入力の場合はTOPへ
  if (to.path != '/' && from.name == undefined) {
    return '/'
  }
})

export default router

ここでポイントがいくつかあります。

hashモード or Historyモード

Vue Routerのデフォルトはhashモードです。
色々違いはあるのですが、私がHistoryモードにしたのは、hashモードだとURLがhttp://localhost:8080/#/hogeみたいに#が付いてしまうから。
違いがあるので要件に合う方を選べば良いかと思います。

HTML5 Historyモード

historyモード選択時は注意点があるのですが、それは後述します。

静的インポート/動的インポート

component: () => import('../components/Test.vue')のような書き方をすることで、各Vueファイルを動的インポートにしています。
これの何が良いかというと、ルート読み込み時に一気に全部のVueを読み込まず、このパスが呼び出された時にインポートしてくれるということです。
つまり、初回読み込みが速くなります。
今回は各ページがそれぞれ重く、またユーザーが全ページを読み込むとは限らないので基本的にこの動的インポートにしました。

じゃあ静的インポートはどないすりゃええかというと、こんな感じ。

静的インポート
import Test from 'Test.vue'
const routes = [
  {
    path: '/',
    name: 'test',
    component: Test,
  }
]

存在しないURLの対応

存在しないURLを指定された場合は、大体Not Foundに飛ばすのかなーと思うのですが、今回は全てTOPにリダイレクトします。

指定外のパスは全てTOPへリダイレクト
routes.push(
  {
    path: '/:catchAll(.*)',
    redirect: '/'
  }
)

このコードですが、例えばルート/pathの場合は正しく制御できても、ルート/path/pathのような指定では上手くリダイレクトできませんでした(指定URLの真っ白ページが表示される)。調査したのですが未解決事項です。

URLの直接指定を拒否

認証後のページを認証前ユーザーに見せたくない、という要件が多そうですが、今回は全てのURL直接指定による遷移を防御したい要件でした。
TOPはOKとして、それ以外の存在するURLが直接指定された場合、パスをTOPに書き換えるという処理をしています。
これはナビゲーションガードという仕組みで実現しました。

URLの直接指定はTOPへリダイレクト
router.beforeEach((to, from) => {
  if (to.path != '/' && from.name == undefined) {
    return '/'
  }
})

「Vue Routerを介さないページ遷移ではfrom.nameに値が入ってこない」ということを利用してガードしています。

main.jsの設定

インストールしたVue Routerを、アプリ全体に読み込ませます。

main.js
import { createApp } from 'vue'
import router from './router'
import App from '../App.vue'

const app = createApp(App)

app.use(router)

app.mount('#app')

.htaccessの設定

Historyモード使用時の注意点です。
これだけでも開発環境では動くので油断するのですが、サーバに上げた途端、404エラーが出ます。
このままではルーティング制御ができないのです。
使用するWebサーバを確認して、それぞれに適した.htaccessファイルを置いてあげる必要があります。
history-mode.html#サーバーの設定例
私の場合はこのApacheの設定を入れてあげるだけでdev同様の動きになりました。
mod_rewriteが使えない場合はもう少し困りそうですね…

全画面SPAの実現

htmlとapp.vueはこんな感じにしました。

index.html
<!-- 割愛 -->
<body>
  <div id="app"></div>
  <script type="module" src="js/main.js"></script>
</body>
App.vue
<template>
  <main>
    <router-view v-slot="{ Component }">
      <Transition name="switch-router">
        <component :is="Component" />
      </Transition>
    </router-view>
  </main>
</template>

これで<router-view>のところに指定されたVueファイルが描画される感じです。

描画したVueファイルでは以下のように指定することで遷移を行っていきます。

Sample.vue
<router-link to="パスの名前" replace>次へ</router-link>

jsで制御する場合はこんな感じ。

Sample.vue
<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()
router.replace('パスの名前')
</script>

Historyモードを使用しているので、router.pushにしてしまうと履歴が残ります。つまり、戻るボタンが使えてしまいます。
それも制御したかったのでrouter.replaceを使用しています。
router-linkを使用する場合は、replaceを記述してあげればOKです。

遷移アニメーションをつける

これで大体動作はOKなのですが、パキパキページが切り替わるのはカッコ悪いのでアニメーションをつけたいです。
遷移ごとにアニメーションを分けたい場合もあるでしょうが、今回は一律同じアニメーションで良かったので、呼び出し元のApp.vueにトランジションアニメーションをつけてしまいました。

App.vue
    <router-view v-slot="{ Component }">
      <Transition name="switch-router">
        <component :is="Component" />
      </Transition>
    </router-view>

※なんでv-slot使っているのか思い出せません。何か理由はありました…何かが動かなくて変えたんです。。。

sample.scss
  .switch-router-enter-active {
    transition: opacity .7s;
    opacity: 0;
  }

  .switch-router-enter-to {
    opacity: 1;
  }

これでなんとなくふんわりしたページ切り替えになりました。

あとがき

VueもViteも初めて触りました。
いろんなサイトの知見を参考にさせていただき、やっとやりたいことが実現できました。
ご指摘や修正は歓迎しています。

1
3
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
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?