W e b I n s i g h t s .

Next.js + WordPress - Part 2

Next.js + WordPress - Part 2

在上一篇文章 Next.js + WordPress – Part 1 我們提到了 Next.js + WordPress 的解決方案可以讓網站跑得更快速以及更安全,但是在寫前篇文章的時候我們只有完成 POC (Prove of Concept),並沒有實作的專案可以跟大家分享。在完成 POC 之後我們立即將因創自己的官網用 Next.js 來改寫,而且在這段期間也有承接了一些 Next.js + WordPress 的案子,所以這篇文章我們會與大家分享我們在開發 Next.js 遇到的問題/解決方法,還有一些建議,最後也可以讓大家看看改用了 Next.js 後的一些數據成果。

如何從 WordPress 抓取資料

因為 Next.js 跟 WordPress 是完全分開的兩個架構,所以如何從 Next.js 抓取 WordPress 資料會變成第一個需要解決的問題,最常見的作法有兩種:

1. GraphQL

GraphQL 是近幾年很流行的工具,在 Headless CMS 生態圈也佔有重要的地位,它可以直接使用 guery 語法去抓取資料,相較於 Rest API 需要透過很多 Endpoints 才能取得一個頁面所需要的所有資料,GraphQL 用一個 query 即可將所需要的東西全部抓到,可以減少因為很多次的 API Request 所產生的延遲。目前也有 GraphQL 的 WordPress 外掛可以直接下載安裝(WPGraphQL)。

雖然 GraphQL 聽起來很方便快速,但可能是我們不夠熟悉的原因,測試過程中遇到了蠻多問題,而且因為很多資料還是需要先做一些邏輯處理才能提供給前端,因此我們主要還是採取第二種方式來抓取資料,但像是menu這些可以直接使用的我們還是會用GraphQL來抓 (我們後來還是都把它寫在API裡面了,因為遇到需要有多層選單的需求)。

[備註]如果是用 GragphQL 抓取資料的話,建議使用像是 Apollo Client 這樣的外掛來將結果快取起來,避免每個頁面都要重複的抓取相同的資料。

WPGraphql
圖片來自 WPGraphql 網站

2. WP REST API

WordPress 本身已經有提供很多 API 的 Endpoints 可以讓我們抓取網站公開資料,甚至連 Block Renderer 都有,不過如同上面所提的,很多資料我們其實都還是需要經過一些邏輯程式或是比較進階的篩選,加上為了避免需要經過很多不同的 Endpoints 才能得到所有的資料,因此我們大部分都是使用 Custom API Endpoints ,並且讓前端可以經由簡短的一兩個 API Request 就能抓取到所需要全部資訊。

跟 GraphQL 相同,建議相同的資料都用快取來加快 Build 的速度,也可以減少不必要的頻寬浪費(畢竟頻寬等於金錢啊),可以使用 memory-cache 或是其他外掛來幫忙快取像是 Menu 或是 Options 這種每個頁面都相同的資料。

要注意的地方:不管是直接抓 post content 或是從 ACF的 欄位抓資料,都要記得把 WordPress (後台CMS)的 URL 取代為 Next.js (前台的網址)。

[非必要] 我們在抓取API資料的時候其實都會加上一個 api key 參數來增加安全性,避免 API Endpoint 被濫用。另外如果有防火牆的話也可以在 Request Header 加上 api key 並設定防火牆來 bypass 有正確 api key 的 request,不止可以加強安全性也可以避免正常的 API Request 被防火牆阻擋。

Yoast SEO資料

相信很多人都會很在意 Yoast SEO 外掛的設定跟資料,包含頁面 Title 以及 og 資料等,甚至是 Breadcrumb ,畢竟這個會影響 SEO,幸運的是 Yoast 其實有一些現有的function可以讓我們將該頁面所有的Yoast資料都抓出來,然後放在 API 的回傳資料裡(當然 GraphQL 也能達成)。

  if (function_exists('YoastSEO')) {

      $yoast = YoastSEO()->meta->for_post($post_id);

      $yoast_head = $yoast->get_head();

      $data['yoast'] = $yoast_head->json;

      $data['breadcrumbs'] = self::get_breadcrumbs($yoast, $post);
  }

上面這段程式,可以把某一個頁面的head資料都抓出來,雖然 Yoast 也能直接產出 breadcrumb,但是還是需要先將 WordPress 的 URL 取代為前端的網址。抓取到 Yoast 的資料後,就可以在每個頁面的 Head 裡面插入這些值。

利用將 Yoast SEO 產生的值放到 Next.js 的 Head 裡面

XML Sitemaps

Sitemap 功能是每一個公開網站都需要的,它可以告訴 Google 哪些網站連結是需要被索引的,一般的 WordPress 網站 Yoast SEO 或是其他外掛都會自動產生 sitemap.xml 檔案,但在 Next.js 目前還是需要自己處理。通常 WordPress 網站的頁面數量都不會是固定,因此都會建議 Sitemap 都是抓取即時的資料來產生,我們用了 next-sitemap 來幫助我們產生 Sitemap 。

簡單的做法就是直接寫一個 API Endpoint 將所有需要公開的連結跟資料都放進去,然後再由 Next.js 來抓取並用 next-sitemap 提供的功能來產生sitemap的內容,範例程式如下:

// pages/sitemap.xml.js
import { getServerSideSitemap } from 'next-sitemap';
import { getSitemap } from '../lib/api';

export const getServerSideProps = async (ctx) => {
  // Method to source urls from cms
  const urls = await getSitemap();

  return getServerSideSitemap(ctx, urls)
};
export default function Sitemap() { }

[提醒]在 WordPress 產生 sitemap 資料的時候,一樣要記得取代URL。

Pagination (分頁)

分頁功能其實是筆者覺得比較麻煩的地方,因為之前 WordPress 都幫我們處理好了,但在 Next.js 我們一樣需要自己處理,雖然可以使用載入更多功能,但是這樣就無法生成 static 頁面,而且為了 SEO 有些頁面還是需要 Pagination 的。

做法上基本上會需要先知道有多少分頁,然後再將每個分頁的連結都在 getStaticPaths 這個 function 回傳,下面的範例程式就是把所有分類的分頁連結都回傳給 Next.js,這樣 Next.js 才會在 build 的時候將這些分頁都先產生出來。

export async function getStaticPaths() {

    const categories = await getCategories();

    let paths = [];

    categories.forEach(category => {

        const totalPages = category.totalPages;

        for (let page = 1; page <= totalPages; page++) {

            paths.push({ params: { category: category.slug, page: page.toString() } });
        }

    });

    return {
        // Opt-in to on-demand generation for non-existent pages
        fallback: 'blocking',
        paths: paths
    }
}

另外在Next.js的category資料夾裡面,會需要再多加兩層資料夾所以會變成page/category/[category]/page/[page].js,如下圖。因為分類的分頁連結是/category/[分類名稱]/page/[分頁數字]

網站圖片

我們建議網站都放在 S3 或是 Google Cloud 這種第三方的圖床,這樣可以避免 WordPress 的網址因為圖片被發現,當然如果都用 Next/Image 也可以,只是要小心它的使用數量限制以及收費標準。

Next.js的網站的成效

上面提了許多用 Next.js + WordPress 來開發網站可能會遇到的問題以及一些提醒,很明顯的,用這樣的方式來開發網站複雜度會增加不少,不過很多東西做過一次就可以重複使用,跟 Next.js 所提供的優點來比較,我們覺得還是非常值得花時間跟心力來開發的。

這邊就讓我們來看看正式網站上線後的表現吧!!

Time to First Byte (TTFB)

在從原本的 WordPress 架構轉為 Next.js 後,TTFB 是我覺得差別最大的部分,如同前一篇文章所提到的,Next.js 會將整個網站部署到 CDN上面,因此理論上 TTFB 會優秀很多是正常的。

從下面的圖片來看,可以明顯看到傳統的 WordPress 架構只會在資料中心(伺服器)的位置附近有比較好的表現,但是 Next.js 的網站卻可以在全世界都有很好的 TTFB 表現(全球平均值為115ms)。

GTmetrix 成績

由下圖可看到 Next.js 網站在 GTmetrix 的各項成績都是非常好的,包含最近 Google 非常在意的 Web Vitals 分數也非常優秀,但是這其實也不意外,有很優秀的 TTFB,頁面static 快取,加上圖片優化以及 assests 都放在 CDN,網站不快都難。

Google Core Web Vitals

Google 2020年推出的 Core Web Vitals (網站體驗核心指標) 是影響搜尋排名一個重要因素,當然 Google 主要還是以網頁內容跟品質為優先關注,但是當有許多競爭者的時候,Web Vitals 就會顯得比較重要,以下是我們客戶的 Next.js 網站上線幾個月過後的 Core Web Vitals 數據,可以看到不管是桌機或是手機版本都有非常好的表現,完全沒有需要改進的頁面。

結論

經過實際專案的經驗,證實 Headless CMS 的解決方案可以將 WordPress 網站轉換成一個快速的靜態網站,並且可以實現更好的性能以及更快的載入速度。除了速度之外,安全性的提升也是不容小覷,可以說是一個非常優秀而且有競爭力的解決方案。

這種開發方式雖然在速度跟安全性上都有極佳的效果,但開發上的複雜度也會增加不少,除了上面提到的資料抓取方式,SEO資料,XML Sitemap跟分頁之外,其他像是表單的處理跟Captcha的驗證,這些都會需要自己處理,雖然對於第一次開發的團隊來說,專案的時間跟困難度都會提升不少,但經過幾次經驗之後應該都能夠慢慢習慣。

Headless CMS 的確是一個非常好也值得研究的解決方案,但並不代表所有網站都應該使用 Headless CMS 的方式來開發,傳統的 WordPress 架構還是有許多好處的,建議還是要根據客戶的需求仔細評估,再來決定開發方式會比較好。

文章最後更新於 : 2023/10/23