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

FeatureReactNext.js
RoutingReact Router (useParams)File-based routing
Data FetchingClient-side (useEffect/React Query)Server-side (async components)
Environment VariablesREACT_APP_ prefixAny name (NEXT_PUBLIC_ for client)
SEOReact Helmet neededBuilt-in metadata API
PerformanceClient-side renderingSSR/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