Skip to content

arnobt78/Headless-Contentful-CMS--React-Fundamental-Project-17

Repository files navigation

Headless Contentful CMS - React Query, Vite, TypeScript, TailwindCSS Fundamental Project 17

License: MIT Vite React TypeScript Tailwind CSS React Query

A modern, production-ready React learning project that demonstrates how to integrate a headless CMS (Contentful) with a typed React frontend using React Query caching, Vite tooling, and Tailwind CSS styling.

Live Demo: https://headless-contentful-cms.vercel.app/

Screenshot 2025-12-02 at 13 32 49 Screenshot 2025-12-02 at 13 33 21

Table of Contents


Project Overview

This project fetches project entries from Contentful CMS and displays them as interactive cards in a responsive layout. It is built for instruction and learning purposes, with practical architecture and production-friendly patterns.

The app uses:

  • Type-safe data structures with TypeScript
  • Smart server-state handling with React Query
  • Clean component composition in React
  • Utility-first styling with Tailwind CSS
  • A headless CMS backend (Contentful) via API

What You Will Learn

By following this project, you can learn:

  • How to connect a React app to a headless CMS
  • How to fetch, transform, and render remote content
  • How React Query handles loading, caching, and deduplication
  • How to keep UI responsive with skeleton loading states
  • How to configure and safely use .env variables in Vite
  • How to structure a modern frontend project for readability and reuse

Features

  • Dynamic project content from Contentful CMS
  • React Query-powered data caching and query management
  • localStorage cache persistence logic in src/main.tsx
  • Skeleton UI while data is loading
  • Responsive grid for mobile, tablet, and desktop
  • Card hover effects and smooth transitions
  • Strong TypeScript typing for API and UI layers
  • Linting support with ESLint v9 flat config

Technology Stack

  • React 19.0.0
  • TypeScript 5.6.3
  • Vite 6.0.0
  • Tailwind CSS 3.4.14
  • TanStack React Query 5.90.11
  • Contentful JavaScript SDK 10.12.0
  • ESLint 9.x

How This Project Works

  1. src/main.tsx bootstraps the app and creates a QueryClient.
  2. React Query options define stale time, retries, and cache behavior.
  3. localStorage cache restore/save is handled before and after query updates.
  4. src/App.tsx composes the page sections (Hero and Projects).
  5. src/Projects.tsx uses useFetchProjects() from src/fetchProjects.tsx.
  6. Contentful data is fetched and mapped into the local Project type.
  7. The UI shows either ProjectSkeleton (loading) or project cards (loaded).

Project Structure

08-contentful-cms/
β”œβ”€β”€ public/
β”‚   └── vite.svg
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ assets/
β”‚   β”‚   β”œβ”€β”€ birthday.png
β”‚   β”‚   β”œβ”€β”€ hero.svg
β”‚   β”‚   β”œβ”€β”€ questions.png
β”‚   β”‚   β”œβ”€β”€ reviews.png
β”‚   β”‚   └── tours.png
β”‚   β”œβ”€β”€ App.tsx
β”‚   β”œβ”€β”€ Hero.tsx
β”‚   β”œβ”€β”€ Projects.tsx
β”‚   β”œβ”€β”€ ProjectSkeleton.tsx
β”‚   β”œβ”€β”€ fetchProjects.tsx
β”‚   β”œβ”€β”€ data.ts
β”‚   β”œβ”€β”€ types.ts
β”‚   β”œβ”€β”€ global.css
β”‚   β”œβ”€β”€ main.tsx
β”‚   └── vite-env.d.ts
β”œβ”€β”€ index.html
β”œβ”€β”€ package.json
β”œβ”€β”€ eslint.config.js
β”œβ”€β”€ tailwind.config.js
β”œβ”€β”€ postcss.config.js
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ tsconfig.node.json
└── vite.config.ts

Detailed File Walkthrough

src/main.tsx

  • Creates and provides the React Query QueryClient
  • Configures cache behavior
  • Restores and persists projects query data in localStorage

src/App.tsx

  • Root layout component
  • Renders Hero and Projects

src/Hero.tsx

  • Intro section with headline and supporting text
  • Responsive image rendering for large screens

src/Projects.tsx

  • Consumes data from custom hook
  • Handles loading state with ProjectSkeleton
  • Renders clickable project cards with animation and hover interactions

src/ProjectSkeleton.tsx

  • Placeholder UI shown during pending API calls
  • Maintains layout and improves perceived performance

src/fetchProjects.tsx

  • Creates Contentful client with API token
  • Fetches entries of content type cmsReactProject
  • Maps CMS payload to app-ready structure (id, title, url, img)

src/types.ts

  • Defines interfaces for Contentful data and app data
  • Provides strict typing for safer refactoring and development

src/data.ts

  • Static fallback/demo project data pattern
  • Useful for understanding expected shape of project entries

src/global.css

  • Tailwind directives
  • Base layer styles
  • Utility animation classes used by project cards

index.html

  • Root HTML entry
  • SEO metadata, social metadata, and structured data setup

API and Backend Overview

This is a frontend project with a managed backend content source:

  • Backend provider: Contentful (Headless CMS)
  • API type: Content Delivery API (read-only)
  • SDK used: contentful

Conceptual Endpoint

GET https://cdn.contentful.com/spaces/{SPACE_ID}/environments/{ENV}/entries?content_type=cmsReactProject

Request in Code

const response = await client.getEntries({
  content_type: "cmsReactProject",
});

Data Transformation

The app maps nested Contentful data into a flat Project model:

  • item.sys.id -> id
  • item.fields.title -> title
  • item.fields.url -> url
  • item.fields.image?.fields?.file?.url -> img

Routes

Current project route behavior is single-page:

  • /

Environment Variables Guide (.env)

This project currently requires one environment variable:

  • VITE_API_KEY

Step 1: Create .env

touch .env

Step 2: Add Required Variable

VITE_API_KEY=your_contentful_content_delivery_access_token

Step 3: Restart Dev Server

After adding or changing env values, restart:

npm run dev

Where It Is Used

  • In src/fetchProjects.tsx
  • Accessed as import.meta.env.VITE_API_KEY

How to Get VITE_API_KEY

  1. Log in to Contentful
  2. Open your target space
  3. Go to Settings -> API keys
  4. Create/select an API key
  5. Copy Content Delivery API - access token
  6. Paste into .env

Security Best Practices

  • Do not commit .env
  • Keep tokens private
  • Rotate token if leaked

Optional Improvement

The current code hardcodes the space ID in src/fetchProjects.tsx. You can also externalize it:

VITE_CONTENTFUL_SPACE_ID=your_space_id
VITE_CONTENTFUL_ENV=master

Contentful CMS Setup

  1. Create a Contentful account and space
  2. Create content type: cmsReactProject
  3. Add fields:
  • title (Short text, required)
  • url (Short text, required)
  • image (Media, optional)
  1. Publish entries for your projects
  2. Verify API key access from CDA settings

Installation and Setup

# Clone repository
git clone <your-repo-url>

# Enter project directory
cd 08-contentful-cms

# Install dependencies
npm install

# Create .env and set VITE_API_KEY
cp .env.example .env

How to Run the Project

Development

npm run dev

Build for Production

npm run build

Preview Production Build

npm run preview

Lint

npm run lint

Code Examples

Custom Hook Usage

import { useFetchProjects } from "./fetchProjects";

const ProjectsSection = () => {
  const { loading, projects } = useFetchProjects();

  if (loading) return <div>Loading...</div>;

  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.title}</li>
      ))}
    </ul>
  );
};

Query Pattern

const { data = [], isPending } = useQuery({
  queryKey: ["projects"],
  queryFn: fetchProjects,
});

Mapping API Data

const mappedProjects = response.items.map((item) => {
  const fields = item.fields as {
    title: string;
    url: string;
    image?: { fields?: { file?: { url?: string } } };
  };

  return {
    id: item.sys.id,
    title: fields.title,
    url: fields.url,
    img: fields.image?.fields?.file?.url,
  };
});

Reusing Components and Logic

You can reuse this codebase in other projects as:

  • CMS-driven portfolio showcase
  • Product/catalog card grid
  • Blog/article card listing
  • Internal dashboard cards from API content

Reuse approach:

  1. Keep useFetchProjects hook pattern
  2. Change content type and field mapping
  3. Reuse ProjectSkeleton for loading UX
  4. Reuse card structure from Projects.tsx
  5. Keep React Query setup for efficient data flow

Troubleshooting

Issue: Projects not showing

  • Check VITE_API_KEY value
  • Confirm cmsReactProject content type exists
  • Ensure entries are published in Contentful
  • Confirm space ID in src/fetchProjects.tsx

Issue: Env changes not applied

  • Restart dev server after editing .env

Issue: Lint/build command fails

  • Run npm install
  • Re-run npm run lint and npm run build

Keywords

react typescript vite tailwindcss react-query tanstack-query contentful headless-cms api-integration frontend-project learning-resource component-architecture skeleton-loading caching modern-web-development


Conclusion

This project is a practical learning reference for building CMS-powered React apps with strong typing, clean architecture, and production-ready data fetching patterns. It is suitable for beginners learning modern frontend development and also useful as a starter template for real projects.


License

This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.

Happy Coding! πŸŽ‰

This is an open-source project - feel free to use, enhance, and extend this project further!

If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.

Enjoy building and learning! πŸš€

Thank you! 😊


About

A modern, production-ready React application demonstrating seamless integration with Contentful headless CMS. This project showcases best practices in TypeScript, React Query for data fetching and caching, Tailwind CSS for styling, and modern React patterns.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors