Back to Documentation
Framework Guide
React
React Integration Guide
Add a blog to your React application with React Router and client-side data fetching
Key Differences from Next.js
- •Routing: Uses React Router's
useParams()
hook to get the slug - •Data Fetching: Client-side only with useEffect or React Query
- •Environment Variables: Must prefix with
REACT_APP_
- •SEO: Requires additional tools like React Helmet for meta tags
1
Install Dependencies
Install the Lightweight Client and React Router for navigation
npm install lightweight-client react-router-dom
# or
yarn add lightweight-client react-router-dom
2
Configure Environment Variables
Create a .env
file in your project root
# .env
REACT_APP_LIGHTWEIGHT_API_KEY=your-api-key-here
Important: React requires the REACT_APP_
prefix for environment variables
3
Create Project Structure
Organize your React app with the following structure
src/
├── App.js
├── pages/
│ ├── BlogList.js
│ └── BlogPost.js
├── services/
│ └── lightweight.js
└── index.js
4
Create Lightweight Service
Create a service to handle all API calls
// src/services/lightweight.js
import { LightweightClient } from 'lightweight-client';
const API_KEY = process.env.REACT_APP_LIGHTWEIGHT_API_KEY;
if (!API_KEY) {
console.warn('REACT_APP_LIGHTWEIGHT_API_KEY is not set. Using demo key.');
}
const client = new LightweightClient(
API_KEY || 'a8c58738-7b98-4597-b20a-0bb1c2fe5772'
);
export const blogService = {
async getPosts(page = 1, limit = 10) {
try {
const response = await client.getPosts(page - 1, limit);
return response;
} catch (error) {
console.error('Error fetching posts:', error);
return { articles: [], total: 0 };
}
},
async getPost(slug) {
try {
const post = await client.getPost(slug);
return post;
} catch (error) {
console.error('Error fetching post:', error);
return null;
}
}
};
5
Setup React Router
Configure routing in your main App component
// src/App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import BlogList from './pages/BlogList';
import BlogPost from './pages/BlogPost';
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route path="/blog" element={<BlogList />} />
<Route path="/blog/:slug" element={<BlogPost />} />
{/* Other routes */}
<Route path="/" element={<div>Home Page</div>} />
</Routes>
</div>
</Router>
);
}
export default App;
6
Create Blog List Component
Component to display all blog posts with pagination
// src/pages/BlogList.js
import React, { useState, useEffect } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { blogService } from '../services/lightweight';
function BlogList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [totalPages, setTotalPages] = useState(0);
const [searchParams, setSearchParams] = useSearchParams();
const currentPage = parseInt(searchParams.get('page') || '1', 10);
useEffect(() => {
fetchPosts();
}, [currentPage]);
const fetchPosts = async () => {
setLoading(true);
try {
const data = await blogService.getPosts(currentPage);
setPosts(data.articles || []);
setTotalPages(Math.ceil(data.total / 10));
} catch (err) {
setError('Failed to load posts');
console.error(err);
} finally {
setLoading(false);
}
};
const handlePageChange = (newPage) => {
setSearchParams({ page: newPage.toString() });
};
if (loading) return <div>Loading posts...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="blog-container">
<h1>Blog</h1>
<div className="posts-grid">
{posts.map((post) => (
<article key={post.id} className="post-card">
<Link to={`/blog/${post.slug}`}>
{post.image && (
<img src={post.image} alt={post.headline} />
)}
<div className="post-content">
{post.category && (
<span className="category">{post.category.title}</span>
)}
<h2>{post.headline}</h2>
<time>
{new Date(post.publishedAt).toLocaleDateString()}
</time>
</div>
</Link>
</article>
))}
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="pagination">
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
Previous
</button>
<span>Page {currentPage} of {totalPages}</span>
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
)}
</div>
);
}
export default BlogList;
7
Create Blog Post Component
Component to display individual blog posts using the slug from URL params
// src/pages/BlogPost.js
import React, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import { blogService } from '../services/lightweight';
function BlogPost() {
const { slug } = useParams(); // Get slug from URL
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchPost();
}, [slug]);
const fetchPost = async () => {
setLoading(true);
try {
const data = await blogService.getPost(slug);
if (!data) {
setError('Post not found');
} else {
setPost(data);
// Update document title
document.title = data.headline;
}
} catch (err) {
setError('Failed to load post');
console.error(err);
} finally {
setLoading(false);
}
};
if (loading) return <div>Loading post...</div>;
if (error) return (
<div>
<h1>404 - Post Not Found</h1>
<Link to="/blog">← Back to blog</Link>
</div>
);
if (!post) return null;
return (
<article className="blog-post">
{/* Breadcrumb */}
<nav className="breadcrumb">
<Link to="/">Home</Link> /
<Link to="/blog">Blog</Link> /
<span>{post.slug}</span>
</nav>
{/* Header */}
<header>
<h1>{post.headline}</h1>
<div className="post-meta">
<time>
{new Date(post.publishedAt).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric'
})}
</time>
{post.readingTime && (
<span> • {post.readingTime} min read</span>
)}
</div>
{post.image && (
<img
src={post.image}
alt={post.headline}
className="featured-image"
/>
)}
</header>
{/* Article Content */}
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: post.html }}
/>
{/* Author */}
{post.author && (
<div className="author-section">
{post.author.image && (
<img
src={post.author.image}
alt={post.author.name}
className="author-image"
/>
)}
<div>
<div className="author-name">{post.author.name}</div>
{post.author.title && (
<div className="author-title">{post.author.title}</div>
)}
</div>
</div>
)}
{/* Tags */}
{post.tags && post.tags.length > 0 && (
<div className="tags">
{post.tags.map((tag, index) => (
<span key={index} className="tag">
{tag}
</span>
))}
</div>
)}
</article>
);
}
export default BlogPost;
Advanced Implementation
Better Data Fetching with React Query
Use React Query for caching, background refetching, and better loading states
// Using React Query for better data fetching
import { useQuery } from '@tanstack/react-query';
import { blogService } from '../services/lightweight';
function BlogPost() {
const { slug } = useParams();
const { data: post, isLoading, error } = useQuery({
queryKey: ['post', slug],
queryFn: () => blogService.getPost(slug),
staleTime: 1000 * 60 * 5, // 5 minutes
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading post</div>;
if (!post) return <div>Post not found</div>;
return (
<article>
{/* Render post content */}
</article>
);
}
SEO Optimization with React Helmet
Add dynamic meta tags for better SEO
// SEO with React Helmet
import { Helmet } from 'react-helmet';
function BlogPost() {
const { slug } = useParams();
const [post, setPost] = useState(null);
// ... fetch logic
return (
<>
<Helmet>
<title>{post?.headline || 'Loading...'}</title>
<meta name="description" content={post?.metaDescription} />
<meta property="og:title" content={post?.headline} />
<meta property="og:description" content={post?.metaDescription} />
<meta property="og:image" content={post?.image} />
<meta property="og:type" content="article" />
</Helmet>
<article>
{/* Post content */}
</article>
</>
);
}
How Slug Routing Works in React
1. Route Definition:
<Route path="/blog/:slug" element={<BlogPost />} />
2. Getting the Slug:
const { slug } = useParams(); // Returns the slug from URL
3. Example:
- URL:
/blog/my-first-post
- Slug value:
my-first-post
React vs Next.js Comparison
Feature | React | Next.js |
---|---|---|
Routing | React Router (useParams) | File-based routing |
Data Fetching | Client-side (useEffect/React Query) | Server-side (async components) |
Environment Variables | REACT_APP_ prefix | Any name (NEXT_PUBLIC_ for client) |
SEO | React Helmet needed | Built-in metadata API |
Performance | Client-side rendering | SSR/SSG options |
Next Steps
- 1.Add Styling: Use CSS, Tailwind, or styled-components for design
- 2.Implement Loading States: Add skeletons or spinners during data fetching
- 3.Add Error Boundaries: Handle errors gracefully with React error boundaries
- 4.Optimize Performance: Implement lazy loading and code splitting