Next.js Integration
Build production-ready Next.js sites with TypeScript and App Router
Setup
1. Environment Variables
Create .env.local:
NEXT_PUBLIC_WP_API_URL=https://yoursite.com/wp-json WP_API_KEY=hb_your_key_here
2. Create WordPress Client
Create lib/wordpress.ts:
const API_URL = `${process.env.NEXT_PUBLIC_WP_API_URL}/bridge/v1`;
export async function getPage(slug: string) {
const res = await fetch(`${API_URL}/page?slug=${slug}`, {
next: { revalidate: 3600 }
});
if (!res.ok) {
throw new Error('Failed to fetch page');
}
return res.json();
}
export async function getPages(limit = 20, offset = 0) {
const res = await fetch(
`${API_URL}/pages?type=post&limit=${limit}&offset=${offset}`,
{ next: { revalidate: 3600 } }
);
return res.json();
}
export async function getAllPostSlugs() {
const data = await getPages(100);
return data.items.map((post: any) => post.slug);
}App Router Examples
Blog Post Page
Create app/blog/[slug]/page.tsx:
import { getPage, getAllPostSlugs } from '@/lib/wordpress';
import { Metadata } from 'next';
type Props = {
params: { slug: string }
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPage(params.slug);
return {
title: post.seo.title,
description: post.seo.description,
openGraph: {
images: [post.seo.ogImage],
},
};
}
export async function generateStaticParams() {
const slugs = await getAllPostSlugs();
return slugs.map((slug: string) => ({ slug }));
}
export default async function BlogPost({ params }: Props) {
const post = await getPage(params.slug);
return (
<article className="max-w-4xl mx-auto px-4 py-16">
<header className="mb-8">
<h1 className="text-5xl font-bold mb-4">{post.title}</h1>
<div className="flex items-center gap-4 text-gray-600">
<span>{post.author.name}</span>
<span>•</span>
<time>{new Date(post.publishedAt).toLocaleDateString()}</time>
</div>
</header>
{post.featuredImage && (
<img
src={post.featuredImage.url}
alt={post.featuredImage.alt}
srcSet={post.featuredImage.srcset}
sizes={post.featuredImage.sizes}
className="w-full rounded-lg mb-8"
/>
)}
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
</article>
);
}Blog List Page
Create app/blog/page.tsx:
import { getPages } from '@/lib/wordpress';
import Link from 'next/link';
export default async function BlogIndex() {
const data = await getPages(20);
return (
<div className="max-w-6xl mx-auto px-4 py-16">
<h1 className="text-5xl font-bold mb-12">Blog</h1>
<div className="grid md:grid-cols-3 gap-8">
{data.items.map((post: any) => (
<Link
key={post.uuid}
href={`/blog/${post.slug}`}
className="border rounded-lg overflow-hidden hover:shadow-lg transition"
>
{post.featuredImage && (
<img
src={post.featuredImage.url}
alt={post.featuredImage.alt}
className="w-full h-48 object-cover"
/>
)}
<div className="p-6">
<h2 className="text-xl font-bold mb-2">{post.title}</h2>
<p className="text-gray-600">{post.excerpt}</p>
</div>
</Link>
))}
</div>
</div>
);
}TypeScript Types
Create types/wordpress.ts:
export interface Post {
uuid: string;
slug: string;
type: string;
status: string;
title: string;
excerpt: string;
content: string;
publishedAt: string;
modifiedAt: string;
author: Author;
seo: SEO;
featuredImage?: FeaturedImage;
categories: Category[];
tags: Tag[];
acf?: Record<string, any>;
}
export interface Author {
id: number;
name: string;
slug: string;
avatar: string;
bio: string;
url: string;
}
export interface SEO {
title: string;
description: string;
canonical: string;
noindex: boolean;
ogImage: string;
}
export interface FeaturedImage {
id: number;
url: string;
alt: string;
width: number;
height: number;
srcset: string;
sizes: string;
}ISR (Incremental Static Regeneration)
Revalidate every hour:
export const revalidate = 3600; // 1 hour
export default async function Page() {
const posts = await getPages();
return <div>{/* ... */}</div>;
}On-Demand Revalidation
Create webhook endpoint app/api/revalidate/route.ts:
import { revalidatePath } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
const slug = body.slug;
revalidatePath(`/blog/${slug}`);
revalidatePath('/blog');
return Response.json({ revalidated: true });
}Deploy to Vercel
- Push your code to GitHub
- Import project in Vercel
- Add environment variables
- Deploy!
Next Steps
- Add pagination to blog list
- Implement category/tag pages
- Set up webhooks for auto-revalidation (Pro)
- Add search with Algolia (Pro)