@itsmrtr/strapi-next-fetch
v1.0.0
Published
Minimal Strapi REST API client for Next.js App Router with built-in cache support
Downloads
113
Maintainers
Readme
strapi-next-fetch
Minimal Strapi REST API client for Next.js App Router with built-in ISR cache support.
Handles auth headers, query string serialization for nested params (filters, populate, pagination),
and Next.js fetch cache options (revalidate, force-cache, no-store).
Why this package?
Strapi has an official JavaScript SDK, but I didn't know about it when I started integrating Strapi into my projects — so I built my own fetch logic from scratch. Over time it became a consistent pattern across projects, so I extracted it into a package.
It's intentionally focused on Next.js App Router and its fetch cache model (revalidate, force-cache, no-store), which is my default stack. If that matches yours, this might save you some boilerplate.
If you prefer the official route, Strapi's SDK is always an option.
Install
# npm
npm install @itsmrtr/strapi-next-fetch
# pnpm
pnpm add @itsmrtr/strapi-next-fetch
# yarn
yarn add @itsmrtr/strapi-next-fetchSetup
Create a single client instance and reuse it across your project:
// lib/strapi.js
import { createStrapiClient } from "@itsmrtr/strapi-next-fetch";
const strapi = createStrapiClient({
url: process.env.STRAPI_API_URL,
token: process.env.STRAPI_API_TOKEN,
});
export default strapi;Usage
Basic fetch
import strapi from "@/lib/strapi";
const data = await strapi.fetch("/api/articles");With fields and populate
const data = await strapi.fetch("/api/articles", {
fields: ["title", "slug", "date"],
populate: {
cover: { fields: ["url"] },
category: { fields: ["title", "slug"] },
},
});With filters and pagination
const data = await strapi.fetch("/api/articles", {
filters: {
featured: { $eq: true },
podcast: { $ne: true },
},
pagination: { page: 1, pageSize: 9 },
sort: ["date:desc"],
});ISR with revalidate (Next.js App Router)
// Revalidate every 15 minutes
const data = await strapi.fetch(
"/api/articles",
{ sort: ["date:desc"] },
{ revalidate: 900 },
);No cache (drafts, live previews)
const data = await strapi.fetch(
"/api/articles",
{ filters: { slug: { $eq: slug } } },
{ cache: "no-store" },
);Daily cache (static content that rarely changes)
const data = await strapi.fetch(
"/api/categories",
{ fields: ["title", "slug"], sort: ["title:asc"] },
{ revalidate: 86400 },
);Draft mode (Strapi v5)
const data = await strapi.fetch(
"/api/articles",
{
filters: { slug: { $eq: slug } },
status: "draft",
},
{ cache: "no-store" },
);Build URL without fetching
const url = strapi.buildUrl("/api/articles", {
filters: { slug: { $eq: "my-article" } },
});
// https://your-strapi.com/api/articles?filters[slug][$eq]=my-articlePractical pattern: query functions per project
Keep your query functions in your own project, using the client:
// lib/queries/articles.js
import strapi from "@/lib/strapi";
export async function getLatestPosts({ page = 1, pageSize = 9 } = {}) {
return strapi.fetch(
"/api/articles",
{
fields: ["title", "slug", "date"],
populate: {
cover: { fields: ["url"] },
category: { fields: ["title", "slug"] },
},
pagination: { page, pageSize },
sort: ["date:desc"],
},
{ revalidate: 900 },
);
}
export async function getSingleArticle(slug) {
return strapi.fetch(
"/api/articles",
{
fields: ["title", "date", "content", "slug"],
populate: {
cover: { fields: ["url", "caption"] },
category: { fields: ["title", "slug"] },
},
filters: { slug: { $eq: slug } },
},
{ revalidate: 900 },
);
}Environment variables
STRAPI_API_URL=https://your-strapi-instance.com
STRAPI_API_TOKEN=your-api-tokenCompatibility
- Next.js 14+ (App Router)
- Strapi v4 and v5
- Node.js 18+
License
MIT
