Blog

<Nuxt.js> Jamstackではpayloadの扱いに気をつけよ。ハマった記録

SSGのつもりが、SSRになってて、大ハマリした記録です。

このサイトのことなのですが、前にBlogにカテゴリ一覧ページをつけました。
ずっと静的で表示していると思っていたのですが、なんとカテゴリー一覧がSSRで表示されていたということに最近気づきました。

なぜ気づいたか

記事を更新した時に、カテゴリー一覧のページに記事が動的に表示されました。
もちろん、ブログトップには表示されていないのでしばらく気づいていなかったのですが、
カテゴリ一覧に飛んでみると、デプロイしてないのに表示されている…。

Nuxt.jsで、SSGでサイト制作する場合は、最終的にnpm run generateコマンドでアプリケーションをビルドし、すべてのルートをHTMLファイルとして生成しdist/ ディレクトリに静的にエクスポートします。
そこでエラーがでていました。このエラーを見逃していました。

原因1 DBから取得してきたデータが間違っていて、ページが生成されていなかった

Nuxt(version2)では、動的ページ(この場合、カテゴリ一覧)を静的エクスポートするには、下記の手順を踏みます。

  1. nuxt.config.jsのgenerateプロパティにて、routeと返却したいpayloadを指定する
  2. ページコンポーネントのasyncDataでpayloadを受け取り、returnしてdataに登録する

しかし、ローカルで制作しているときは、ページコンポーネント内で直接API接続して情報を取得している(実際はStoreを使っています)ので、SSRで表示しています。

静的ページを確認するにはエクスポートしないといけないので、制作中は静的ページができてないことに、当時気がついていませんでした。

エラーで静的ページが生成されなかった場合はSSRで表示されるということは、今回初めて知りました。
(ロジックがいまいちわかっていないだけですが。)

WordPressから取得したデータを確認する

記事作成はWordPressを使用しています。
functions.phpでregister_rest_routeを用いてエンドポイントを作成し、データを取得しています。
動的ページはnuxt.config.jsのgenerateプロパティ内で、API通信をしています。

この部分でちゃんとデータを取得し、ページコンポーネントにpayloadがしっかりと渡ってきているのかを調べることにしました。

function add_rest_original_endpoint() {
  //エンドポイントを登録
  register_rest_route('wp/custom', '/myposts/(?P<paged>\d+)', array(
    'methods' => 'GET',
    'args' => array(
      'paged' => array(
        'default' => 1,
        'sanitize_callback' => 'absint'
      )
    ),
    'callback' => 'get_myposts',
  ));
}
add_action('rest_api_init', 'add_rest_original_endpoint');

function get_myposts($r) {
  $rersult = array();

  if($r -> get_param('posts_per_page') != null){
    $posts_per_page = intval($r -> get_param('posts_per_page'));
  }else{
    $posts_per_page = -1;
  }

  $query = new WP_Query(array(
    'name' => $r -> get_param('slug'),
    'category_name' => $r -> get_param('cat'),
    'posts_per_page' => $posts_per_page,
    'paged' => $r -> get_param('paged')
  ));
  $articles = array();
  
  while($query->have_posts()){
    $query -> the_post();
    $article = array();
    $article['id'] = get_the_ID();
    $article['date'] = get_the_time('Y年n月j日');
    
    //省略〜〜
    //$articleに記事データを入れていく

    // カテゴリ
    $theCategries = get_the_category($post->ID);
    $cat = [];
    foreach($theCategries as $category){
      $cat[] = $category->slug; //ここが間違ってた
    }
    $article['cat'] = $cat;

    // articlesに入れる
    $articles[] = $article;
  }
  $result['articles'] = $articles;
  
  // 全てのカテゴリー
  $categories = get_categories();
  $allCatList = [];
  foreach($categories as $cat){
    $allCatList[] = array(
      'catname' => $cat->name,
      'slug' => $cat->slug
    );
  }
  $result['category'] = $allCatList;

  // PHPの配列をreturnすると、JSONになる。
  return $result;
}

$resultに、記事情報であるarticle配列と、カテゴリー一覧の$category配列をまとめてreturnします。

ここで、記事に付与したカテゴリーが間違っていることに気づきました。slugを返さなければいけないところを、カテゴリ名(日本語のラベル)を返してしまっていました。
具体的には$cat[] = $category->slug;のところが$cat[] = $category->name;になっていました。

修正すると、generateでのエラーはなくなり、
blog/category/●●
が生成されていることが確認できました。

原因2 payloadを別の動的ページで加工してしまっていた

ここからがハマったところです。

payloadで受け取った記事がなぜか少ない

generateのエラーはなくなりましたが、カテゴリー一覧に表示されている記事数がやけに少ないことに気がつきました。
10記事くらいあるのに3記事くらいしか表示されていない。
ページコンポーネントで受け取ったpayloadの中身をみてみることにしました。

export default {
  〜
  async asyncData({ params, error, payload, store, $axios }) {
    //generateコマンドで静的ページ生成する場合。nuxt.config.jsでAPI通信し、レスポンス(payload)を受け取る
    console.log(payload) //なぜか15記事しかはいっていない
    if(payload){
      const allPosts = payload.articles;
      const catPosts = allPosts.filter(post => {
        return post['cat'] === params.cateslug;
      })
      return catPosts;
    }else{
      //SSR storeでAPI通信
      return {
        catPosts: await store.dispatch('getCatPost',params.cateslug)
      }
    }
  }
}

API通信で全記事を受け取り、下記でそのページのカテゴリの記事を抽出しています。

const catPosts = allPosts.filter(post => {
  return post['cat'] === params.cateslug;
})

全記事が入っているはずのpayloadに、なぜか15記事しか入っていませんでした。
それが特定カテゴリだけに絞り込んで3記事になってしまっているということです。

functions.phpを穴があくほどみても全記事取得しているはずなのに、
なぜpayloadに15記事しか入っていないのかがわからず、悩みました。

余談ですが、asyncDataの中でデータを確認するのは大変です…。
上記のconsole.logでpayloadを確認するために、いちいちgenerateコマンドを叩かないといけません。
いいデバッグ方法はないのだろうか…と色々調べたのですが、うまくいきませんでした。
できそうな気はするのでまた頑張ってみようと思います。

別ページのasyncData内でpayloadを加工していた

15というキーワードでプロジェクト内を探ると、記事の詳細ページにて、新着記事を15記事に分割しているところがありました。
ブログ詳細ページの新着記事は、PCでこの記事を見られてる方なら右カラムにあります。

新着記事がサムネ付きで15記事ならんでいるのですが、ここを表示するために書いている部分が下記となります。

async asyncData({ params, error, payload, store, $axios }) {
  if (payload){
    // 15記事にする
    payload[0].articles.length > 15 ? payload[0].articles.splice(15) : '';
    return {
      newBlog : payload[0],
      currentPost : payload[1],
      recoPost : payload[2]
    }
  }else{
    〜
  }
},

categoryとは関係のないページの別ファイルですが、ここを仮に15から25に変更すると、category一覧のページコンポーネント側で受け取ったpayloadが、25記事になっていました。
つまり、_slug.vueでpayloadを変更したことが、_cateslug.vueに影響しているということです。

_slug.vueと_cateslug.vueのディレクトリは下記のようになっています。

_slug.vueでは、下記でpayloadに破壊的変更を加えていました。

payload[0].articles.length > 15 ? payload[0].articles.splice(15) : '';

splice() メソッドは、その場で既存の要素を取り除いたり、置き換えたり、新しい要素を追加したりすることで、配列の内容を変更します。
この部分で、payloadそのものを中身が15の配列に変更しています。

不思議なのは、それがなぜか_cateslug.vueで引き継がれ(引き継がれているのかどうかは、内部的な処理がわからないので自信ありませんが)、
_cateslugのasyncDataの引数に既に15個になって入ってきました。

解決法

slug.vueの方のpayloadに破壊的な変更を加えないことにしました。

async asyncData({ params, error, payload, store, $axios }) {
  if (payload){
    // 15記事にする。新たにnewBlogArr[]を作成
    let newBlogArr = [];
    if(payload[0].articles.length > 15){
      for(let i=0; i<15; i++){
        newBlogArr.push(payload[0].articles[i]);
      }
    }
    const newBlogArticle = { articles:newBlogArr }
    return {
      newBlog : newBlogArticle,
      currentPost : payload[1],
      recoPost : payload[2]
    }
  }else{
    〜
  }
},

これで、_cateslug.vueの方のpayloadに、しっかりと全記事入るようになりました。

まとめ

今回のことでnuxt.jsについてわかったことは下記の2点です。

  • 静的htmlをエクスポートできていなかった場合は、自動的にSSRになる(開発用にSSR用の処理も書いていることが前提。payloadの有無で条件分岐しているからかもしれない)
  • asyncDataの引数でわたされるpayloadは、他のページコンポーネントで破壊的変更を加えていた場合、それが他のページコンポーネントのpayloadにも影響する

次の課題は、asyncDataでのデバッグです。いちいちデータの中身を確認するためにgenerateするのは時間がかかるし、骨が折れました。
どうにかそちらもやり方を見つけたいと思っています。

おすすめの記事 recommend blog

新着 new blog

github