React Integration
Integrate Headless Bridge with your React application (Create React App, Vite, or any React setup).
Quick Start
1. Environment Variables
Create a .env file:
REACT_APP_WP_API_URL=https://your-wordpress-site.com/wp-json/bridge/v1 REACT_APP_WP_API_KEY=your_api_key_here # Optional, for preview content
For Vite, use VITE_ prefix instead:
VITE_WP_API_URL=https://your-wordpress-site.com/wp-json/bridge/v1 VITE_WP_API_KEY=your_api_key_here
2. API Client
Create src/services/wordpress-client.js:
const API_URL = process.env.REACT_APP_WP_API_URL ||
process.env.VITE_WP_API_URL;
const API_KEY = process.env.REACT_APP_WP_API_KEY ||
process.env.VITE_WP_API_KEY;
async function apiFetch(endpoint, preview = false) {
const headers = { 'Content-Type': 'application/json' };
if (API_KEY && preview) {
headers['X-Headless-Bridge-Key'] = API_KEY;
}
const response = await fetch(`${API_URL}${endpoint}`, { headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
export async function getPostBySlug(slug, preview = false) {
return apiFetch(
`/page?slug=${encodeURIComponent(slug)}${preview ? '&preview=true' : ''}`,
preview
);
}
export async function getPosts(options = {}) {
const { type = 'post', limit = 20, offset = 0 } = options;
const params = new URLSearchParams({ type, limit, offset });
return apiFetch(`/pages?${params}`);
}Usage Examples
Blog Post List
import { useState, useEffect } from 'react';
import { getPosts } from './services/wordpress-client';
function BlogList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
getPosts({ type: 'post', limit: 10 })
.then(data => {
setPosts(data.items);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
return (
<div className="blog-list">
{posts.map(post => (
<article key={post.uuid}>
{post.featuredImage && (
<img
src={post.featuredImage.url}
alt={post.featuredImage.alt}
/>
)}
<h2>{post.title}</h2>
<div dangerouslySetInnerHTML={{ __html: post.excerpt }} />
<a href={`/blog/${post.slug}`}>Read More</a>
</article>
))}
</div>
);
}Single Post
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { getPostBySlug } from './services/wordpress-client';
function BlogPost() {
const { slug } = useParams();
const [post, setPost] = useState(null);
useEffect(() => {
getPostBySlug(slug).then(setPost);
}, [slug]);
if (!post) return <div>Loading...</div>;
return (
<article>
<h1>{post.title}</h1>
{post.author && (
<div className="author">
<img src={post.author.avatar} alt={post.author.name} />
<span>{post.author.name}</span>
</div>
)}
<time>{new Date(post.publishedAt).toLocaleDateString()}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}Custom Hook
Create reusable hooks for cleaner code:
// hooks/useWordPress.js
import { useState, useEffect } from 'react';
import { getPostBySlug, getPosts } from '../services/wordpress-client';
export function usePost(slug) {
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!slug) return;
getPostBySlug(slug)
.then(setPost)
.catch(setError)
.finally(() => setLoading(false));
}, [slug]);
return { post, loading, error };
}
export function usePosts(options = {}) {
const [posts, setPosts] = useState([]);
const [pagination, setPagination] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
getPosts(options)
.then(data => {
setPosts(data.items);
setPagination(data.pagination);
setLoading(false);
});
}, [JSON.stringify(options)]);
return { posts, pagination, loading };
}
// Usage:
// const { post, loading } = usePost('my-post-slug');
// const { posts, pagination } = usePosts({ type: 'post', limit: 10 });React Router Setup
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import BlogList from './components/BlogList';
import BlogPost from './components/BlogPost';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<BlogList />} />
<Route path="/blog/:slug" element={<BlogPost />} />
</Routes>
</BrowserRouter>
);
}Pagination
function PaginatedBlogList() {
const [posts, setPosts] = useState([]);
const [pagination, setPagination] = useState({
total: 0, offset: 0, limit: 10
});
const loadPosts = (offset = 0) => {
getPosts({ type: 'post', limit: 10, offset })
.then(data => {
setPosts(data.items);
setPagination(data.pagination);
});
};
useEffect(() => loadPosts(0), []);
const currentPage = Math.floor(pagination.offset / pagination.limit) + 1;
const totalPages = Math.ceil(pagination.total / pagination.limit);
return (
<div>
<div className="posts">
{posts.map(post => (
<article key={post.uuid}>
<h2>{post.title}</h2>
</article>
))}
</div>
<div className="pagination">
<button onClick={() => loadPosts(pagination.offset - pagination.limit)}>
Previous
</button>
<span>Page {currentPage} of {totalPages}</span>
<button onClick={() => loadPosts(pagination.offset + pagination.limit)}>
Next
</button>
</div>
</div>
);
}SEO with React Helmet
Install: npm install react-helmet-async
import { Helmet } from 'react-helmet-async';
function BlogPost({ slug }) {
const { post } = usePost(slug);
return (
<>
<Helmet>
<title>{post.seo?.title || post.title}</title>
<meta name="description" content={post.seo?.description} />
<meta property="og:title" content={post.seo?.title} />
<meta property="og:image" content={post.featuredImage?.url} />
<link rel="canonical" href={post.seo?.canonical} />
</Helmet>
<article>
{/* Post content */}
</article>
</>
);
}React Query Integration
For advanced caching and state management:
import { useQuery } from '@tanstack/react-query';
import { getPostBySlug } from './services/wordpress-client';
function usePost(slug) {
return useQuery({
queryKey: ['post', slug],
queryFn: () => getPostBySlug(slug),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Usage:
const { data: post, isLoading, error } = usePost('my-post-slug');Performance Tips
1. Image Lazy Loading
<img
src={post.featuredImage.url}
alt={post.featuredImage.alt}
loading="lazy"
srcSet={post.featuredImage.srcset}
sizes={post.featuredImage.sizes}
/>2. Memoization
import { useMemo } from 'react';
const sortedPosts = useMemo(() => {
return posts.sort((a, b) =>
new Date(b.publishedAt) - new Date(a.publishedAt)
);
}, [posts]);3. Code Splitting
import { lazy, Suspense } from 'react';
const BlogPost = lazy(() => import('./components/BlogPost'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<BlogPost />
</Suspense>
);
}CORS Configuration
If developing locally, add to WordPress wp-config.php:
header('Access-Control-Allow-Origin: http://localhost:3000');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-Headless-Bridge-Key');Or use a proxy in package.json (CRA):
{
"proxy": "http://localhost:8080"
}Next Steps
- Add search functionality
- Implement infinite scroll
- Add loading skeletons
- Cache API responses
- Add error boundaries
- Implement offline support