Vue/Nuxtアプリケーションにおけるレンダリングの仕組みについて CSRとSSRって何?

PROGRAMING
PROGRAMING

はじめに

今回はVueアプリケーションにおけるCSRとSSR、そしてライフサイクルフックについて詳しく解説していきます。

この記事は、CSRやSSRという聞き馴染みのない用語について詳しく解説していきます。

ちなみにSSRはよく聞く超レア、という意味ではありません。

こんな方におすすめの記事となっています!

  • CSR(クライアントサイドレンダリング)って何?
  • SSR(サーバーサイドレンダリング)って何?
  • Nuxtのライフサイクルフックについて知りたい

CSR(クライアントサイドレンダリング)とは

CSRは「Client Side Rendering」の略で、ブラウザ(クライアント)側でWebページを構築する方法です。

レゴブロックで家を作ることを想像してください。

  1. お店(サーバー)からレゴのパーツ(JavaScriptのファイル)が届く
  2. あなた(ブラウザ)が説明書(Vueのコード)を見ながら家を組み立てる
  3. 完成した家(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のメリット

  1. サーバーの負担が少ない
  2. ページ遷移が速い
  3. インタラクティブな操作がスムーズ

CSRのデメリット

  1. 初期表示が遅い
  2. SEO(検索エンジン最適化)が難しい
  3. 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のメリット

  1. 初期表示が速い
  2. SEOに有利
  3. JavaScriptが無効でも基本的な表示が可能

SSRのデメリット

  1. サーバーの負担が大きい
  2. 実装が複雑になりやすい
  3. サーバーのコストが高くなる

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の特徴:

  1. ファイルベースのルーティング
  2. 自動コード分割
  3. SSR/CSRの切り替えが容易
  4. 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();
    }
  }
}
  1. 最初は空のページが表示される
  2. その後データを取得して表示が更新される
  3. ローディング中の表示が必要

SSRの場合(Nuxt)

// pages/blog/index.vue
export default {
  async asyncData({ $axios }) {
    // サーバーサイドでデータを取得
    const posts = await $axios.$get('/api/posts');
    return { posts }
  },
  
  mounted() {
    // クライアントサイドでの追加処理
    console.log('ページが表示されました');
  }
}
  1. サーバーでデータを取得して完成したHTMLを送信
  2. ブラウザですぐにコンテンツが表示される
  3. その後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(/* ... */)
    }
  }
}

ライフサイクルフック使い分けのまとめ

  1. データ取得
    • SSR必要: asyncData または fetch
    • クライアントのみ: created または mounted
  2. DOM操作
    • mounted フックを使用(SSRでは実行されない)
  3. 監視のセットアップ
    • mounted でセットアップ
    • beforeDestroy でクリーンアップ
  4. 初期化処理
    • データ/計算のみ: created
    • DOM必要: mounted
  5. クリーンアップ
    • beforeDestroy を使用

まとめ

CSRとSSRとでは、主にデータ取得のタイミングや役割が異なります。

またそれぞれに適した使用場面があります。

これらのライフサイクルフックを適切に使い分けることで、効率的で堅牢なアプリケーションを構築できます。

タイトルとURLをコピーしました