iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

WordPress再進化,用Next.js拆分前端,實現UI完全客製化及優化效能與SEO系列 第 10

Day10 在 Next.js 安裝 apollo-graphql,串接 WordPress GraphQL API(下)

上一篇我們成功在 Next.js 安裝 ApolloClient 了,今天我們繼續使用剛安裝的 ApolloClient 從 WordPress 抓取文章資料,在首頁顯示文章列表!

在 GraphiQL 決定需要的欄位,生成 query

首先打開 WordPress 後台的 GraphiQL IDE,在左邊 Explorer 裡面有各種可 query 的欄位,其中 posts 就是文章列表。展開它並勾選你想使用的欄位,如果不確定欄位意義的話,可以展開右邊 Docs,裡面可以找到每個欄位的簡短說明文字,或是可以將所有想了解的欄位都打勾,直接按執行按鈕,看回傳值是什麼,這是比較快速的方式。

Imgur

我決定在我首頁文章列表,使用圖中所示的欄位,並且要依照日期由新到舊降序排序,以及只抓取前十篇,於是 GraphiQL 幫我生成的 query string 如下:

query MyQuery {
  posts(where: { orderby: { field: DATE, order: DESC } }, first: 10) {
    edges {
      node {
        databaseId
        title
        uri
        date
        excerpt
        featuredImage {
          node {
            sourceUrl
            altText
          }
        }
      }
    }
  }
}

Next.js 端實作

讓我們把這段 query 複製進 Next.js 程式碼裡,我建立了一個 graphql 資料夾,用來存放所有 GraphQL query string 和資料型態轉換的邏輯,並在裡面建立了 allPostsQuery.js(完整路徑是 /graphql/allPostsQuery.js),內容如下:

import { gql } from '@apollo/client'

import discardPTag from '../utils/discardPTag'

export const ALL_POSTS_QUERY = gql`
  query allPosts($first: Int!) {
    posts(where: { orderby: { field: DATE, order: DESC } }, first: $first) {
      edges {
        node {
          databaseId
          title
          uri
          date
          excerpt
          featuredImage {
            node {
              sourceUrl
              altText
            }
          }
        }
      }
    }
  }
`

export const allPostsQueryVars = {
  first: 10,
}

export const transformAllPostsData = (data) => {
  return (
    data?.posts?.edges
      ?.map((edge) => edge?.node)
      ?.map((post) => ({
        id: post?.databaseId || '',
        title: post?.title || '',
        uri: post?.uri || '',
        date: post?.date || '',
        excerpt: discardPTag(post?.excerpt) || '',
        featuredImage: {
          sourceUrl: post?.featuredImage?.node?.sourceUrl || '',
          altText: post?.featuredImage?.node?.altText || '',
        },
      })) || []
  )
}

裡面放了從 GraphiQL 複製過來的 query string,並且我額外寫了 transformAllPostsData function,用來在等等將 query 回來的資料轉成用起來舒服的資料格式,把串接介面切乾淨。

其中值得注意的是,excerpt 欄位我用來當作文章簡介,這在編輯文章時有欄位可以輸入,若沒有輸入的話,WordPress 也會自動生成,給文章一個摘要,但是這個欄位在 WPGraphQL query 回來時,是會多出前綴後綴 p tag 的,像是 <p>Real Content</p>,於是我在 transformAllPostsData 裡面多用了另一個 discardPTag function 來移除多餘 p tag,實作寫在 /utils/discardPTag.js 裡頭:

/**
 * Discard starting and trailing <p> from a string
 * "<p>slice</p>" -> "slice"
 * @param {string} source
 * @returns string
 */
export default function discardPTag(source) {
  return source?.slice(3, -4)
}

接著在首頁的 /pages/index.js 裡,參照 Next.js 官方 with-apollo 範例寫法,在最下面新增 getStaticProps function 來在 server side 產生 ApolloClient 並 query GraphQL API,並在 component 內部使用 useQuery 來使用 query 回來的資料,並用剛剛的 transformAllPostsData 轉換資料,存成 allPosts 陣列,並在 render 時用 array.map 產生文章列表,修改完的完整 index.js 長這樣:

import { useMemo } from 'react'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import { useQuery } from '@apollo/client'

import styles from '../styles/Home.module.css'

import { initializeApollo, addApolloState } from '../lib/apolloClient'
import { allPostsQueryVars, ALL_POSTS_QUERY, transformAllPostsData } from '../graphql/allPostsQuery'

export default function Home() {
  const { data } = useQuery(ALL_POSTS_QUERY, {
    variables: allPostsQueryVars,
  })
  const allPosts = useMemo(() => transformAllPostsData(data), [data]) || []

  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>就。很。Pro。blog</h1>

        <div className={styles.grid}>
          {allPosts?.map((post) => (
            <Link key={post.id} href={post.uri} passHref>
              <a className={styles.card}>
                <h2>{post.title}</h2>
                <p>{post.excerpt}</p>
              </a>
            </Link>
          ))}
        </div>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <span className={styles.logo}>
            <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
          </span>
        </a>
      </footer>
    </div>
  )
}

export async function getStaticProps() {
  const apolloClient = initializeApollo()

  await apolloClient.query({
    query: ALL_POSTS_QUERY,
    variables: allPostsQueryVars,
  })

  return addApolloState(apolloClient, {
    props: {},
    revalidate: 1,
  })
}

最後為了讓樣式稍微能看,限制文章 title 和簡介的行數,我修改了 /styles/Home.module.css,在大約 100 行處的 .card h2 和 .card p 都加了 line-clamp 的樣式,修改完長這樣:

/* ... */
/* At about line 100 */
.card h2 {
  margin: 0 0 1rem 0;
  font-size: 1.5rem;

  display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	text-overflow: ellipsis;
  overflow: hidden;
}

.card p {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.5;

  display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	text-overflow: ellipsis;
  overflow: hidden;
}
/* ... */

最後執行 yarn dev,打開瀏覽器進到 http://localhost:3000/ ,你應該就會看到如下畫面,首頁成功顯示了基本文章列表!恭喜你!

Imgur

這篇文章的相關改動,可以參照這支 commit

下一篇

下一篇我們會繼續來優化首頁的樣式,我們會安裝 Tailwindcss 這套最近非常熱門的 CSS 框架,來簡單切版!


上一篇
Day9 在 Next.js 安裝 apollo-graphql,串接 WordPress GraphQL API(上)
下一篇
Day11 TailwindCSS 介紹,在 Next.js 專案安裝 TailwindCSS
系列文
WordPress再進化,用Next.js拆分前端,實現UI完全客製化及優化效能與SEO14

尚未有邦友留言

立即登入留言