未分類
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としてリンク部分をクリックすればリロードすることなく個別ページに遷移されるのがわかると思います。