Build Phases
Phase 2 — API Layer
All WordPress data fetching lives in one file: src/lib/wordpress.ts
TypeScript Types
// src/lib/wordpress.ts
const WP_API = process.env.NEXT_PUBLIC_WP_API_URL;
export interface WPPost {
id: number;
slug: string;
title: { rendered: string };
excerpt: { rendered: string };
content: { rendered: string };
date: string;
modified: string;
featured_media: number;
_embedded?: {
"wp:featuredmedia"?: Array<{
source_url: string;
alt_text: string;
media_details: { sizes: {
medium_large?: { source_url: string };
large?: { source_url: string };
full: { source_url: string };
}};
}>;
};
}
export interface WPProject extends WPPost {
acf: {
project_category?: string;
project_partner?: string;
project_overview?: string;
project_location?: string;
};
}Fetch Functions
export async function getAllPosts(perPage = 12): Promise<WPPost[]> {
const res = await fetch(
`${WP_API}/posts?_embed&per_page=${perPage}&status=publish`,
{ next: { revalidate: 3600 } }
);
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`);
return res.json();
}
export async function getPostBySlug(slug: string): Promise<WPPost | null> {
const res = await fetch(`${WP_API}/posts?slug=${slug}&_embed`,
{ next: { revalidate: 3600 } });
if (!res.ok) return null;
const posts: WPPost[] = await res.json();
return posts[0] ?? null;
}
export async function getLatestPost(): Promise<WPPost | null> {
const res = await fetch(`${WP_API}/posts?_embed&per_page=1&status=publish`,
{ next: { revalidate: 1800 } });
if (!res.ok) return null;
const posts: WPPost[] = await res.json();
return posts[0] ?? null;
}
export async function getAllProjects(): Promise<WPProject[]> {
const res = await fetch(`${WP_API}/projects?_embed&per_page=100&status=publish`,
{ next: { revalidate: 3600 } });
if (!res.ok) throw new Error(`Failed to fetch projects: ${res.status}`);
return res.json();
}
export async function getProjectBySlug(slug: string): Promise<WPProject | null> {
const res = await fetch(`${WP_API}/projects?slug=${slug}&_embed`,
{ next: { revalidate: 3600 } });
if (!res.ok) return null;
const projects: WPProject[] = await res.json();
return projects[0] ?? null;
}Image Helper Functions
export function getFeaturedImageUrl(
post: WPPost,
size: "large" | "full" = "large"
): string {
const media = post._embedded?.["wp:featuredmedia"]?.[0];
if (!media) return "/images/fallback.jpg";
if (size === "large") {
return media.media_details?.sizes?.large?.source_url
?? media.media_details?.sizes?.medium_large?.source_url
?? media.source_url;
}
return media.source_url;
}
export function getFeaturedImageAlt(post: WPPost): string {
return post._embedded?.["wp:featuredmedia"]?.[0]?.alt_text
?? post.title.rendered;
}Always use ?_embed in API requests to get featured images in a single request. Without it you would need a second request per post to get the image URL.
Test from Terminal
curl "https://cms.yourdomain.com/wp-json/wp/v2/posts?_embed&per_page=1"
# If you get JSON -> connected
# If you get HTML -> check WordPress permalink settings