choblog

未分類

2025/04/06

キャッチアップ画像

headless-wordpress (5) 詳細ページ

~ 目次 ~

    1 詳細ページ作成

    前回までで投稿リストを表示させることができました、今回はそれぞれのリストのリンクをクリックしたら個々のページに遷移する流れを解説していきます。

    まずは肝心の詳細ページを作成

    frontend-next /
    └── src/
        └── app/
            ├── page.tsx
            └── singlePost/
                └── [slug]/
                    └── page.tsx

    app直下のpage.tsx内のリンクのhrefを

    /singlePost/${post.slug}

    とすることで、localhost.net/singlePost/スラッグ名〜に遷移、詳細ページ内で「スラッグ名」を受け取って、個々のページデータを取得〜表示することができます。

    app/page.tsx

    import Image from 'next/image'
    import { getPostsList } from '../data/getPostsList'
    import Link from 'next/link'
    
    export default async function Home() {
        const posts = await getPostsList()
    
        return (
            <div className="grid gap-3">
                <h1 className="border border-red-500">headless-wordpress</h1>
                <ul className="grid gap-3 border border-blue-500 p-3">
                    {posts.map((post) => {
                        return (
                            <li key={post.id} className="border border-yellow-500">
                                <Link href={`/singlePost/${post.slug}`}>
                                    <h2>{post.title}</h2>
                                    <div className="relative h-24 w-24">
                                        <Image
                                            priority
                                            fill
                                            className="object-cover"
                                            src={post.featuredImage.sourceUrl}
                                            alt="キャッチアップ画像"
                                            unoptimized
                                        />
                                    </div>
                                    <div
                                        dangerouslySetInnerHTML={{
                                            __html: post.excerpt,
                                        }}
                                    ></div>
                                </Link>
                            </li>
                        )
                    })}
                </ul>
            </div>
        )
    }
    

    詳細ページ、singlePost/[slug]/page.tsx

    import { getSinglePost } from '@/src/data/getSinglePost'
    
    const SinglePost = async ({
        params,
    }: {
        params: Promise<{ slug: string }>
    }) => {
        const { slug } = await params
        const post = await getSinglePost({ slug })
    
        return (
            <div>
                <div>{post.title}</div>
                <div>{post.content}</div>
            </div>
        )
    }
    
    export default SinglePost
    

    フェッチ関数、data/getSinglePost.ts

    import { fetchGraphQL } from '../api/fetchGraphQL'
    import { SinglePostQuery } from '../query/postQuery'
    import { singlePostResponseType } from '../type/singlePostResponseType'
    
    export async function getSinglePost({ slug }: { slug: string }) {
        try {
            const res: singlePostResponseType = await fetchGraphQL(
                SinglePostQuery,
                {
                    id: slug,
                },
            )
    
            const post = {
                content: res.post.content,
                date: res.post.date,
                id: res.post.id,
                modified: res.post.modified,
                slug: res.post.slug,
                title: res.post.title,
                categories: {
                    name: res.post.categories.edges[0].node.name,
                    slug: res.post.categories.edges[0].node.slug,
                },
                featuredImage: {
                    sourceUrl: res.post.featuredImage.node.sourceUrl,
                },
            }
            return post
        } catch (error) {
            console.error('Error in getPost:', error)
            throw error
        }
    }
    

    関数に渡すクエリとレスポンスデータの型

    query/singlePostQuery.ts

    export const singlePostQuery = `
    query singlePostQuery($id: ID!) {
      post(id: $id, idType: SLUG) {
        content
        date
        id
        modified
        slug
        title
        categories {
          edges {
            node {
              name
              slug
            }
          }
        }
        featuredImage {
          node {
            sourceUrl
          }
        }
      }
    }
    `
    

    type/singlePostResponseType.ts

    export interface singlePostResponseType {
        post: {
            content: string
            date: string
            id: string
            modified: string
            slug: string
            title: string
    
            categories: {
                edges: {
                    node: {
                        name: string
                        slug: string
                    }
                }[]
            }
            featuredImage: {
                node: {
                    sourceUrl: string
                }
            }
        }
    }
    
    

    2 generateStaticParams

    最後に generateStaticParams を使ってビルド時に各記事の slug を取得〜あらかじめ詳細ページを静的に生成するようにしておきます。これにより、ページ遷移がネイティブアプリのようにサクサク動くようになりユーザー体験が向上、加えてGoogle のクローラーがページ内容を認識しやすくなるため、SEO 対策としても効果的です。

    singlePost/[slug]/page.tsx

    import { getSinglePost } from '@/src/data/getSinglePost'
    + import { getSlugsList } from '@/src/data/getSlugsList'
    
    + export async function generateStaticParams() {
    +    const paths = await getSlugsList()
    +    return paths
    + }
    
    const SinglePost = async ({
        params,
    }: {
        params: Promise<{ slug: string }>
    }) => {
        const { slug } = await params
        const post = await getSinglePost({ slug })
    
        return (
            <div>
                <div>{post.title}</div>
                <div>{post.content}</div>
            </div>
        )
    }
    
    export default SinglePost
    

    data/getSlugsList/ts

    import { fetchGraphQL } from '../api/fetchGraphQL'
    import { slugsListResponseType } from '../type/slugsListResponseType'
    import { slugsListQuery } from '../query/slugsListQuery'
    
    export async function getSlugsList() {
        try {
            const res: slugsListResponseType = await fetchGraphQL(slugsListQuery)
    
            return res.posts.edges.map((data) => ({
                slug: data.node.slug,
            }))
        } catch {
            throw new Error('Failed to fetch slug')
        }
    }
    

    query/slugsListQuery.ts

    export const slugsListQuery = `
    query slugsListQuery {
      posts(first: 10000) {
        edges {
          node {
            slug
          }
        }
      }
    }
    `

    slugsListResponseType.ts

    export type slugsListResponseType = {
        posts: {
            edges: {
                node: {
                    slug: string
                }
            }[]
        }
    }
    

    SSG(Static Site Generation / 静的サイト生成)は本番環境じゃないと意味がないため、docker-compose.ymlファイルの”yarn dev”をコメントアウトして”yarn build && yarn start”部分のコメントアウトを外します。

      frontend-next:
        container_name: frontend-next
        extra_hosts:
          - "host.docker.internal:host-gateway"
        build:
          context: ../../
          dockerfile: docker/development/Dockerfile
        ports:
          - "3000:3000"
        environment:
          - WATCHPACK_POLLING=true
        volumes:
          - ../../frontend-next:/app
          - ../../frontend-next/node_modules:/app/node_modules
        # command: sh -c "yarn dev"
        command: sh -c "yarn build && yarn start"
        restart: always
    

    そうしたら

    docker compose down
    docker compose up -d

    とします。ビルドに少し時間がかかりますが

    docker logs frontend-next

    としてビルドが完了するのを確認〜ブラウザでlocalhost.netとしてリンク部分をクリックすればリロードすることなく個別ページに遷移されるのがわかると思います。

    広告

    広告

    広告

    広告

    広告

    広告