はじめに
今回はVueアプリケーションにおけるCSRとSSR、そしてライフサイクルフックについて詳しく解説していきます。
この記事は、CSRやSSRという聞き馴染みのない用語について詳しく解説していきます。
ちなみにSSRはよく聞く超レア、という意味ではありません。
こんな方におすすめの記事となっています!
- CSR(クライアントサイドレンダリング)って何?
- SSR(サーバーサイドレンダリング)って何?
- Nuxtのライフサイクルフックについて知りたい
CSR(クライアントサイドレンダリング)とは
CSRは「Client Side Rendering」の略で、ブラウザ(クライアント)側でWebページを構築する方法です。
レゴブロックで家を作ることを想像してください。
- お店(サーバー)からレゴのパーツ(JavaScriptのファイル)が届く
- あなた(ブラウザ)が説明書(Vueのコード)を見ながら家を組み立てる
- 完成した家(webページ)が見えるようになる
実際のコードで例えると:
<!-- index.html -->
<div id="app">
<!-- 最初は空っぽ -->
</div>
<script src="vue.js"></script>
<script src="app.js"></script>
// app.js
new Vue({
el: '#app',
data: {
message: 'こんにちは!'
},
mounted() {
// ブラウザで実行される
console.log('ページが表示されました!')
}
})
CSRのメリット
- サーバーの負担が少ない
- ページ遷移が速い
- インタラクティブな操作がスムーズ
CSRのデメリット
- 初期表示が遅い
- SEO(検索エンジン最適化)が難しい
- JavaScriptが無効な環境では動作しない
SSR(サーバーサイドレンダリング)とは
SSRは「Server Side Rendering」の略で、サーバー側でWebページを構築する方法です。
同じくレゴの例で説明すると:
1. お店(サーバー)で店員さんが家を組み立てる
2. 完成した家(HTML)があなた(ブラウザ)に届く
3. すぐに完成した家(Webページ)が見える
実際のコードで例えると:
// server.js
const app = new Vue({
data: {
message: 'こんにちは!'
},
template: '<div>{{ message }}</div>'
});
// サーバーでHTMLを生成
const renderer = require('vue-server-renderer').createRenderer();
renderer.renderToString(app).then(html => {
console.log(html); // '<div>こんにちは!</div>'
});
SSRのメリット
- 初期表示が速い
- SEOに有利
- JavaScriptが無効でも基本的な表示が可能
SSRのデメリット
- サーバーの負担が大きい
- 実装が複雑になりやすい
- サーバーのコストが高くなる
Nuxtを使用する場合のCSRとSSR
Nuxtは、VueベースのフレームワークでSSRを簡単に実装できるように設計されています。
Nuxtでのシンプルな例:
// pages/index.vue
export default {
data() {
return {
message: 'こんにちは!'
}
},
// サーバーサイドで実行される
async asyncData() {
const data = await fetch('https://api.example.com/data');
return { apiData: data }
},
// クライアントサイドで実行される
mounted() {
console.log('ブラウザで表示されました!');
}
}
Nuxtの特徴:
- ファイルベースのルーティング
- 自動コード分割
- SSR/CSRの切り替えが容易
- SEOに最適化された構造
VueとNuxtのライフサイクルフックの違い
Vueのライフサイクルフック
Vueのライフサイクルは、インスタンスの生成から破棄までの一連の流れを管理します。
主なフック:
export default {
beforeCreate() {
// インスタンス作成前
},
created() {
// インスタンス作成後
},
beforeMount() {
// DOM追加前
},
mounted() {
// DOM追加後
},
beforeUpdate() {
// 再描画前
},
updated() {
// 再描画後
},
beforeDestroy() {
// 破棄前
},
destroyed() {
// 破棄後
}
}
Nuxtのライフサイクルフック
Nuxtは、Vueのライフサイクルフックに加えて、サーバーサイドレンダリングのための特別なフックを提供します。
export default {
// サーバーサイドのみ
asyncData() {
// データ取得
},
// サーバーサイド&クライアントサイド
fetch() {
// ストア更新用データ取得
},
// サーバーサイドのみ
middleware() {
// ページ遷移前の処理
},
// サーバーサイドのみ
validate() {
// パラメータ検証
}
}
実践的な例で理解を深める
シンプルなブログアプリケーションを例に説明します:
CSRの場合
// BlogList.vue
export default {
data() {
return {
posts: []
}
},
mounted() {
// ページ表示後にデータ取得
this.fetchPosts();
},
methods: {
async fetchPosts() {
const response = await fetch('/api/posts');
this.posts = await response.json();
}
}
}
- 最初は空のページが表示される
- その後データを取得して表示が更新される
- ローディング中の表示が必要
SSRの場合(Nuxt)
// pages/blog/index.vue
export default {
async asyncData({ $axios }) {
// サーバーサイドでデータを取得
const posts = await $axios.$get('/api/posts');
return { posts }
},
mounted() {
// クライアントサイドでの追加処理
console.log('ページが表示されました');
}
}
- サーバーでデータを取得して完成したHTMLを送信
- ブラウザですぐにコンテンツが表示される
- その後JavaScriptが読み込まれてインタラクティブになる
使い分けのポイント
CSRが適している場合:
- 管理画面など、SEOを気にしないアプリケーション
- ユーザーのインタラクションが多いアプリケーション
- リアルタイムで更新が必要なアプリケーション
// 例:リアルタイムチャット
export default {
data() {
return {
messages: []
}
},
mounted() {
this.connectWebSocket();
},
methods: {
connectWebSocket() {
this.socket = new WebSocket('ws://...');
this.socket.onmessage = this.handleMessage;
}
}
}
SSRが適している場合:
- ブログや企業サイトなど、SEOが重要なサイト
- 初期表示の速度が重要なサイト
- コンテンツが頻繁に更新されないサイト
// pages/blog/_id.vue
export default {
async asyncData({ params, $axios }) {
const post = await $axios.$get(`/api/posts/${params.id}`);
return { post }
},
head() {
return {
title: this.post.title,
meta: [
{ hid: 'description', content: this.post.summary }
]
}
}
}
Nuxtで使用するライフサイクルフックの詳細
Nuxtは、Vueのライフサイクルフックに加えて、サーバーサイドレンダリングのための特別なフックを提供します。
1. middleware
ページやレイアウトがロードされる前に実行されます。
// middleware/auth.js
export default function ({ store, redirect }) {
// ユーザーが未ログインの場合、ログインページにリダイレクト
if (!store.state.isAuthenticated) {
return redirect('/login')
}
}
// pages/secret.vue
export default {
middleware: 'auth', // このページにミドルウェアを適用
}
2. asyncData
ページコンポーネントがロードされる前にデータを取得します。
// pages/posts/_id.vue
export default {
async asyncData({ params, $axios }) {
try {
// 記事データの取得
const post = await $axios.$get(`/api/posts/${params.id}`)
return { post } // コンポーネントのdataにマージされます
} catch (error) {
console.error(error)
}
},
// データが取得できなかった場合のフォールバック
data() {
return {
post: null
}
}
}
3. fetch
Vuexストアのデータを設定するために使用します。
// pages/users.vue
export default {
async fetch({ store }) {
try {
// ユーザーリストを取得してストアに保存
await store.dispatch('users/fetchUsers')
} catch (error) {
console.error(error)
}
}
}
4. validate
動的ルートのパラメータを検証します。
// pages/posts/_id.vue
export default {
validate({ params }) {
// IDが数値であることを確認
return /^\d+$/.test(params.id)
}
}
実践的な例:ブログ記事ページ
// pages/posts/_id.vue
export default {
// 1. まずミドルウェアでの認証チェック
middleware: 'auth',
// 2. パラメータの検証
validate({ params }) {
return /^\d+$/.test(params.id)
},
// 3. データの取得
async asyncData({ params, $axios }) {
const post = await $axios.$get(`/api/posts/${params.id}`)
return { post }
},
// 4. Vueのライフサイクルフック
data() {
return {
comments: []
}
},
async created() {
// コメントの取得
await this.fetchComments()
},
mounted() {
// ビューポートの監視を開始
this.setupIntersectionObserver()
},
beforeDestroy() {
// クリーンアップ
this.observer.disconnect()
},
methods: {
async fetchComments() {
const response = await this.$axios.$get(`/api/posts/${this.post.id}/comments`)
this.comments = response.data
},
setupIntersectionObserver() {
this.observer = new IntersectionObserver(/* ... */)
}
}
}
ライフサイクルフック使い分けのまとめ
- データ取得
- SSR必要:
asyncData
またはfetch
- クライアントのみ:
created
またはmounted
- SSR必要:
- DOM操作
mounted
フックを使用(SSRでは実行されない)
- 監視のセットアップ
mounted
でセットアップbeforeDestroy
でクリーンアップ
- 初期化処理
- データ/計算のみ:
created
- DOM必要:
mounted
- データ/計算のみ:
- クリーンアップ
beforeDestroy
を使用
まとめ
CSRとSSRとでは、主にデータ取得のタイミングや役割が異なります。
またそれぞれに適した使用場面があります。
これらのライフサイクルフックを適切に使い分けることで、効率的で堅牢なアプリケーションを構築できます。