Build fast, scalable sites with modern JS. Headless CMS Integration Support team delivers secure setup and smooth API integration.

Headless CMS with JavaScript and TypeScript Made Simple

A headless CMS with JavaScript or TypeScript helps you build fast and flexible websites. It gives developers more control while content teams can manage content easily.

In this article, you will learn why headless is a good choice, see simple ways to fetch content, and explore practical examples with WordPress and Strapi.

Why Choose Headless with JS and TypeScript

Headless CMS with JavaScript and TypeScript Made Simple

Headless CMS with JavaScript and TypeScript Made Simple

If you want a faster website, better Google rankings, and more control, headless CMS with modern JavaScript is a strong option.

  • Better Performance and SEO

When you use a headless CMS with tools like Next.js or Nuxt.js, pages load faster because they are prepared in advance. Search engines can read them easily, which helps improve rankings.

Result for you:

  • Faster load times
  • Better user experience
  • Improved SEO
  • Easy for Content Teams

Editors can still use familiar platforms like WordPress or Strapi to manage content. They write and publish as usual without touching code.

  • More Freedom for Developers

Developers can build the front end using tools like React or Vue.js. They are not limited by themes, which makes the site easier to scale and maintain.

What Are Your Options

If you want to fetch content from a headless CMS using JavaScript or TypeScript, you have three simple options.

1. Fetch API

Fetch is already built into the browser. You do not need to install anything.

Good choice if:

  • Your project is small
  • You only need simple requests
  • You want to keep things basic

It is easy and works well for straightforward tasks.

Start Your Headless Project

Chat animation


2. Axios

Axios is a popular library for handling API calls.

Good choice if:

  • Your app makes many requests
  • You need better error handling
  • You want features like request control and cancellation

It gives you more control than Fetch.

3. GraphQL

GraphQL lets you ask for only the data you need.

Good choice if:

  • Your content is complex
  • You want fewer API calls
  • You are using TypeScript and want strong type support

It keeps your data clean and precise.

How to Implement It

Below are simple, practical examples you can use in real projects. You can copy, adjust the API URL and token, and start testing right away.

A. Fetch API (JavaScript and TypeScript)

Use Fetch if you want something simple and built in.

JavaScript Example

// fetch-example.js
async function getPosts() {
try {
const res = await fetch('https://cms.example.com/wp-json/wp/v2/posts?_embed', {
headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' }
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error('Fetch error:', err);
return [];
}
}
// Usage in any JavaScript application:
getPosts().then(posts => {
console.log('Fetched posts:', posts);
}).catch(error => {
console.error('Error fetching posts:', error);
});

TypeScript Example
// fetch-example.ts
export interface Post {
id: number;
title: { rendered: string };
content: { rendered: string }
}
export async function getPosts(): Promise<Post[]> {
const res = await fetch('https://cms.example.com/wp-json/wp/v2/posts?_embed');
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json();
}
// Usage with TypeScript
async function displayPosts() {
const posts: Post[] = await getPosts();
posts.forEach(post => {
console.log(`Title: ${post.title.rendered}`);
});
}
B. Axios with Interceptors (JavaScript and TypeScript)

Use Axios if you want more control, especially for larger apps.

TypeScript Example

// axios-example.ts
import axios, { AxiosInstance } from 'axios';
export interface Page {
id: number;
title: { rendered: string }
}
const api: AxiosInstance = axios.create({
baseURL: 'https://cms.example.com/wp-json',
timeout: 5000,
});
api.interceptors.request.use(cfg => {
cfg.headers!['Authorization'] = `Bearer YOUR_API_TOKEN`;
return cfg;
});
api.interceptors.response.use(
response => response,
error => {
console.error('Axios error:', error.response?.status);
return Promise.reject(error);
}
);
export async function fetchPages(): Promise<Page[]> {
const res = await api.get<Page[]>('/wp/v2/pages');
return res.data;
}

Usage Example
import { fetchPages } from './axios-example';
async function displayPages() {
try {
const pages = await fetchPages();
pages.forEach(page => {
console.log(`Page: ${page.title.rendered}`);
});
} catch (error) {
console.error('Error fetching pages:', error);
}
}
displayPages();
C. GraphQL Queries (JavaScript and TypeScript)

Use GraphQL if you want only specific fields and fewer requests.

JavaScript Example

// graphql-example.js
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://cms.example.com/graphql',
cache: new InMemoryCache(),
});
export async function getPostsGraphQL() {
const { data } = await client.query({
query: gql`
query GetPosts {
posts {
nodes {
id
title
content
}
}
}
`
});
return data.posts.nodes;
}
// Usage
async function displayGraphQLPosts() {
try {
const posts = await getPostsGraphQL();
posts.forEach(post => {
console.log(`Post: ${post.title}`);
});
} catch (error) {
console.error('GraphQL error:', error);
}
}

TypeScript Example
// graphql-example.ts
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
interface GraphQLPost {
id: string;
title: string;
content: string;
}
const client = new ApolloClient({
uri: 'https://cms.example.com/graphql',
cache: new InMemoryCache(),
});
export async function getPostsGraphQL(): Promise<GraphQLPost[]> {
const { data } = await client.query({
query: gql`
query GetPosts {
posts {
nodes {
id
title
content
}
}
}
`
});
return data.posts.nodes;
}
D. Fetching Content from Strapi

Strapi supports both REST and GraphQL.

D1. Strapi REST with Relations
const qs = require('qs');
const query = qs.stringify({
fields: ['title', 'slug'],
populate: {
headerImage: {
fields: ['name', 'url'],
},
},
}, { encodeValuesOnly: true });
const response = await fetch(`https://api.example.com/api/articles?${query}`, {
headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' }
});
const data = await response.json();
console.log(data);

This fetches selected fields and related images.

D2. Strapi REST with Filtered Relations
const qs = require('qs');
const query = qs.stringify({
populate: {
categories: {
sort: ['name:asc'],
filters: {
name: { $eq: 'Cars' },
},
},
},
}, { encodeValuesOnly: true });
const response = await fetch(`https://api.example.com/api/articles?${query}`, {
headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' }
});
const data = await response.json();
console.log(data);

This filters and sorts related categories.

D3. Strapi GraphQL Example
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache(),
});
export async function getStrapiArticles() {
const { data } = await client.query({
query: gql`
query GetArticles {
articles {
data {
id
attributes {
title
slug
headerImage {
data {
attributes {
name
url
}
}
}
}
}
}
}
`
});
return data.articles.data;
}

Why This Setup Works Well

When you use a headless CMS with modern JavaScript and TypeScript, you gain clear advantages.

Key Advantages
  • Better performance: Static generation and server rendering help your pages load quickly. Fast pages improve SEO and user experience.
  • Better developer workflow: TypeScript adds type safety. It catches mistakes early and keeps your code easier to manage over time.
  • Easy to scale: Since the frontend and backend are separate, you can scale them independently. This works well for high traffic sites.
  • More freedom: You can choose any frontend framework while keeping one central content system.
  • Stronger security: Your CMS is not directly exposed to users. API only access reduces common attack risks.

Common Challenges and Simple Fixes

Even strong setups face issues. Here are practical solutions.

Challenge 1: API Rate Limits

If you send too many requests, your CMS may block them.

Fix: Add caching

// Cache with TTL
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function cachedFetch(url) {
const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const data = await fetch(url).then(res => res.json());
cache.set(url, { data, timestamp: Date.now() });
return data;
}

This reduces repeated requests and improves speed.

Challenge 2: Security Risks

API tokens and user content must be handled carefully.

Fix: Secure tokens and clean content

import DOMPurify from 'dompurify';
// Secure API client
const createSecureClient = (baseURL) => {
return axios.create({
baseURL,
timeout: 10000,
headers: {
'Authorization': `Bearer ${process.env.API_TOKEN}`,
'Content-Type': 'application/json'
}
});
};
// Sanitize HTML content
const sanitizeContent = (html) => DOMPurify.sanitize(html);

Always store tokens in environment variables and clean any HTML before rendering.

Challenge 3: Errors and Broken Pages

Network failures happen. Your app should handle them properly.

Fix: Add safe error handling

interface FetchResult<T> {
data: T | null;
error: string | null;
loading: boolean;
}
async function safeFetch<T>(url: string): Promise<FetchResult<T>> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return { data, error: null, loading: false };
} catch (error) {
console.error('Fetch error:', error);
return {
data: null,
error: error instanceof Error ? error.message : 'Unknown error',
loading: false
};
}
}

This keeps your UI stable even when something fails.

Performance Tips

To keep your site fast:

  • Fetch only the fields you need
  • Use pagination for large datasets
  • Add browser and CDN caching
  • Optimize images with modern formats
  • Use code splitting to reduce bundle size
Security Best Practices

To protect your project:

  • Store API keys in environment variables
  • Sanitize user generated content
  • Configure CORS properly
  • Apply rate limiting
  • Validate all input data
Important Notes

These examples use WordPress and Strapi as examples, but the same ideas work with any headless CMS. You only need to adjust the API endpoints.

  • Never store API tokens in your code repository.
  • Use field selection and pagination to improve performance.
  • Add logging and monitoring to track API errors.

When you plan carefully and apply these practices, your headless setup becomes fast, secure, and ready to grow.

[Need assistance with a different issue? Our team is available 24/7.]

Conclusion

Headless with JavaScript and TypeScript gives you speed, flexibility, and a clean structure for growth.

If you’re ready to move forward, connect with us and let’s build the right setup for your project.