These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
Strapi is the leading open-source headless CMS offering features, like customizable APIs, role-based permissions, multilingual support, etc. It simplifies content management and integrates effortlessly with modern frontend frameworks.
Explore the Strapi documentation for more details.
Next.js is a popular React framework known for its performance and ease of use. It includes features such as server-side rendering (SSR), static site generation (SSG), and other advanced features, which make it a go-to choice for modern web development.
Visit the Next.js documentation for more.
The out-of-the-box Strapi features allow you to get up and running in no time: 1. Single types: Create one-off pages that have a unique content structure 2. Customizable API: With Strapi, you can just hop into your code editor and edit the code to fit your API to your needs. 3. Integrations: Strapi supports integrations with Cloudinary, SendGrid, Algolia, and others. 4. Editor interface: The editor allows you to pull in dynamic blocks of content. 5. Authentication: Secure and authorize access to your API with JWT or providers. 6. RBAC: Help maximize operational efficiency, reduce dev team support work, and safeguard against unauthorized access or configuration modifications. 7. i18n: Manage content in multiple languages. Easily query the different locales through the API.
Learn more about Strapi features.
We are going to start by setting up our Strapi 5 project with the following command:
ποΈ Note: Make sure that you have created a new directory for your project.
You can find the full documentation for Strapi 5 here.
npx create-strapi-app@latest serverYou will be asked to choose if you would like to use Strapi Cloud. We will choose to skip for now.
π Welcome to Strapi! Ready to bring your project to life?
Create a free account and get:
30 days of access to the Growth plan, which includes:
β¨ Strapi AI: content-type builder, media library, and translations
β
Live Preview
β
Single Sign-On (SSO) login
β
Content History
β
Releases
? Please log in or sign up.
Login/Sign up
β― Skip
After that, you will be asked how you would like to set up your project. We will choose the following options:
? Do you want to use the default database (sqlite) ? Yes
? Start with an example structure & data? Yes <-- make sure you say yes
? Start with Typescript? Yes
? Install dependencies with npm? Yes
? Initialize a git repository? YesOnce everything is set up and all the dependencies are installed, you can start your Strapi server with the following command:
cd server
npm run developYou will be greeted with the Admin Create Account screen.
Go ahead and create your first Strapi user. All of this is local, so you can use whatever you want.
Once you have created your user, you will be redirected to the Strapi Dashboard screen.
Since we created our app with the example data, you should be able to navigate to your Article collection and see the data that was created for us.
Now, let's make sure that all of the data is published. If not, you can select all items via the checkbox and then click the Publish button.
Once all your articles are published, we will expose our Strapi API for the Articles Collection. This can be done in Settings -> Users & Permissions plugin -> Roles -> Public -> Article.
You should have find and findOne selected. If not, go ahead and select them.
Now, if we make a GET request to http://localhost:1337/api/articles, we should see the following data for our articles.
ποΈ Note: that article covers (images) are not returned. This is because the REST API, by default, does not populate any relations, media fields, components, or dynamic zones. Learn more about REST API: Population & Field Selection.
So let's get the article covers by using the populate=* parameter: http://localhost:1337/api/articles?populate=*
Nice, now that we have our Strapi 5 server set up, we can start to set up our Next.js application.
Run the command below to locally create your new Next.js app, depending on your package manager.
The command below should do the following:
nextjs-projectnextjs-project project to start the dev server.npm
# npm
npx create-next-app@latest nextjs-project --yes
cd nextjs-project
npm run devyarn
# yarn
yarn create next-app@latest nextjs-project --yes
cd nextjs-project
yarn devpnpm
# pnpm
pnpm create next-app@latest nextjs-project --yes
cd nextjs-project
pnpm devbun
# bun
bun create next-app@latest nextjs-project --yes
cd nextjs-project
bun dev
βNOTE
The
--yesskips prompts using the default setup: TypeScript, Tailwind, ESLint, App Router, and Turbopack, with import alias @/*.
Visit http://localhost:3000 to view your new Next.js project.
To learn more about Next.js installation, visit the installation page.
To fetch content from Strapi, execute a GET request for the content type. In your case, you will fetch article entries in your Strapi backend.
Be sure that you activated the find permission for the Article collection type.
ποΈ NOTE: We also want to fetch covers (images) of articles, so we have to use the
populateparameter as seen below.
We will fetch contents from Strapi using two strategies:
useEffect hook)fetch API)use hook)useEffect hook)Here, you will use the useEffect React hook.
1
2
3
4
5
6
7
8
9
10
11
12
// step 1: create a function to fetch articles
const getArticles = async () => {
const response = await fetch(`${STRAPI_URL}/api/articles?populate=*`);
const data = await response.json();
setArticles(data.data);
};
// step 2: Fetch articles on component mount
useEffect(() => {
getArticles();
}, []);You can use any HTTP client such as Axios or Fetch
Using Axios
1
2
3
4
5
import axios;
// fetch articles along with their covers
const response = await axios.get("http://localhost:1337/api/articles?populate=*");
console.log(response.data.data);Using Fetch
1
2
3
4
// fetch articles along with their covers
const response = await fetch("http://localhost:1337/api/articles?populate=*");
const articles = await response.json();
console.log(articles.data);fetch API)To fetch data in a server component, turn your component into an asynchronous function and await the fetch call.
For example:
1
2
3
4
5
6
7
8
9
10
11
// step 1: fetch content asynchronously
export default async function Page() {
const response = await fetch("http://localhost:1337/api/articles?populate=*");
const articles = await response.json();
...
}
// step 2: display or map over content
{
articles.data.map((article) => <li key={article.id}>{article.title}</li>);
}use hook)First, fetch articles in a server component and stream back to a client component with the Suspense boundary and the use hook:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// SERVER COMPONENT
// step 1: Import client component and Suspense inside server Component
import Articles from "@/app/ui/articles";
import { Suspense } from "react";
// step 2: fetch data inside server component
// Don't await the data fetching function
const articles = getArticles();
// step 3: stream data to client component
return (
<Suspense fallback={<div>Loading...</div>}>
<Articles articles={articles} />
</Suspense>
);
...
// CLIENT COMPONENT
// step 4: import `use` hook
"use client";
import { use } from "react";
// step 5: read the promise
const allArticles = use(articles);
// step 6: display or map over content
{
allArticles.map((article) => <li key={article.id}>{article.title}</li>);
}Now, let's implement these 3 strategies with the same kind of example.
In this project example, you will fetch data from Strapi and display it in your Next.js server component using the fetch API.
Head over to your Next.js entry file ./src/app/page.tsx to proceed.
Add your Strapi backend URL to the environment variable file .env.local for the Strapi API and to bundle the Strapi URL to the browser.
1
2
3
# Path: ./.env
NEXT_PUBLIC_STRAPI_URL=http://localhost:1337
Image component and Allow ImagesImport the Image component, which provides automatic image optimization.
1
2
3
// Path: ./src/app/page.tsx
import Image from "next/image";Since you are working on a development environment on localhost, you have to configure Next.js to allow images.
Navigate to ./next.config.ts and add the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Path: ./next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "http",
hostname: "localhost",
port: "1337",
pathname: "/**",
},
],
dangerouslyAllowLocalIP: true,
},
};
export default nextConfig;With the dangerouslyAllowLocalIP, Next.js will now allow the Image Optimization API to process images from localhost.
fetch APINow, fetch article entries using the fetch API.
1
2
3
4
5
6
// Path: ./src/app/page.tsx
const response = await fetch(
`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/articles?populate=*`,
);
const articles = await response.json();Create a helper function to format the publishedAt date of the article into a human-readable format (MM/DD/YYYY).
1
2
3
4
5
6
7
8
9
10
// Path: ./src/app/page.tsx
const formatDate = (date: Date) => {
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "2-digit",
day: "2-digit",
};
return new Date(date).toLocaleDateString("en-US", options);
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Path: ./src/app/page.tsx
<div className="p-6">
<h1 className="text-4xl font-bold mb-8">Next.js and Strapi Integration</h1>
<div>
<h2 className="text-2xl font-semibold mb-6">Articles</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{articles.data.length > 0 ? (
articles.data.map((article) => (
<article
key={article.id}
className="bg-white shadow-md rounded-lg overflow-hidden"
>
<Image
className="w-full h-48 object-cover"
src={process.env.NEXT_PUBLIC_STRAPI_URL + article.cover.url}
alt={article.title}
width={180}
height={38}
priority
/>
<div className="p-4">
<h3 className="text-lg font-bold mb-2">{article.title}</h3>
<p className="text-gray-600 mb-4">{article.content}</p>
<p className="text-sm text-gray-500">
Published: {formatDate(article.publishedAt)}
</p>
</div>
</article>
))
) : (
<div>Nothing to fetch at the moment!</div>
)}
</div>
</div>
</div>;Now, this is what your Next.js project should look like:
Awesome, congratulations!
Next, let's use the traditional useEffect hook.
useEffectIn this project example, you will fetch data from Strapi and display it in your Next.js application using the React useEffect hook.
Head over to your Next.js entry file ./src/app/page.tsx to proceed.
Add your Strapi backend URL to the environment variable file .env.local for the Strapi API and to bundle to the browser.
1
2
3
# Path: ./.env
NEXT_PUBLIC_STRAPI_URL=http://localhost:1337
"use client" DirectoryInside the ./src/app/page.tsx file, add the code below:
1
2
3
// Path: ./src/app/page.tsx
"use client"; // This is a client-side componentThe "use client" directive will tell Next.js to render a component on the client-side instead of the default server directive.
Import the React hooks useEffect and useState for side effects and state management, respectively.
Also, import the Image component, which provides automatic image optimization.
1
2
3
4
5
6
7
// Path: ./src/app/page.tsx
"use client"; // This is a client-side component
// Import React hooks and Image component
import { useEffect, useState } from "react";
import Image from "next/image";Since you are working on a development environment on localhost, you have to configure Next.js to allow images.
Navigate to ./next.config.ts and add the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Path: ./next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "http",
hostname: "localhost",
port: "1337",
pathname: "/**",
},
],
dangerouslyAllowLocalIP: true,
},
};
export default nextConfig;With the dangerouslyAllowLocalIP, Next.js will now allow the Image Optimization API to process images from localhost.
articles State VariableCreate a state variable articles to hold the articles data fetched from Strapi.
1
2
3
4
// Path: ./src/app/page.tsx
// Define articles state
const [articles, setArticles] = useState();Create an asynchronous function that fetches the articles from the Strapi API. The data fetched is passed to setArticles to update the articles state.
1
2
3
4
5
6
7
8
9
10
// Path: ./src/app/page.tsx
// Fetch articles
const getArticles = async () => {
const response = await fetch(
`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/articles?populate=*`,
);
const data = await response.json();
setArticles(data.data);
};Create a helper function to format the publishedAt date of the article into a human-readable format (MM/DD/YYYY).
1
2
3
4
5
6
7
8
9
10
// Path: ./src/app/page.tsx
const formatDate = (date: Date) => {
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "2-digit",
day: "2-digit",
};
return new Date(date).toLocaleDateString("en-US", options);
};Use the useEffect to run the getArticles function when the component mounts.
1
2
3
4
5
// Path: ./src/app/page.tsx
useEffect(() => {
getArticles();
}, []);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Path: ./src/app/page.tsx
<div className="p-6">
<h1 className="text-4xl font-bold mb-8">Next.js and Strapi Integration</h1>
<div>
<h2 className="text-2xl font-semibold mb-6">Articles</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{articles?.length > 0 ? (
articles.map((article) => (
<article
key={article.id}
className="bg-white shadow-md rounded-lg overflow-hidden"
>
<Image
className="w-full h-48 object-cover"
src={process.env.NEXT_PUBLIC_STRAPI_URL + article.cover.url}
alt={article.title}
width={180}
height={38}
priority
/>
<div className="p-4">
<h3 className="text-lg font-bold mb-2">{article.title}</h3>
<p className="text-gray-600 mb-4">{article.content}</p>
<p className="text-sm text-gray-500">
Published: {formatDate(article.publishedAt)}
</p>
</div>
</article>
))
) : (
<div>Nothing to fetch at the moment!</div>
)}
</div>
</div>
</div>;Now, this is what your Next.js project should look like:
Awesome, congratulations!
In this example, you will fetch data from a server component and stream to a client component using the use hook and Suspense boundary.
Add your Strapi backend URL to the environment variable file .env.local for the Strapi API and to bundle to the browser.
1
2
3
# Path: ./.env
NEXT_PUBLIC_STRAPI_URL=http://localhost:1337
Inside your server component, import the Suspense boundary and the client component that you want to stream data to.
1
2
3
4
// Path: ./src/app/page.tsx
import { Suspense } from "react";
import Articles from "./ui/articles";Next, create a getArticles() function to fetch articles from your Strapi backend.
1
2
3
4
5
6
7
8
9
10
// Path: ./src/app/page.tsx
// function to fetch articles
const getArticles = async () => {
const response = await fetch(
`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/articles?populate=*`,
);
const articles = await response.json();
return articles;
};Pass the promise to your Client Component as a prop. Do not await the getArticles() function because it should be a promise.
Notice the fallback component while we await the promise.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Path: ./src/app/page.tsx
export default async function Home() {
const articles = getArticles();
return (
<div className="p-6">
<h1 className="text-4xl font-bold mb-8">
Next.js and Strapi Integration
</h1>
<Suspense fallback={<div>Loading....</div>}>
<Articles articles={articles} />
</Suspense>
</div>
);
}The <Articles> component is wrapped in a <Suspense> boundary. This means the fallback component will be shown while the promise is being resolved.
Inside the app folder, create a folder called ui. Inside the ui folder, create a file called articles.tsx.
Inside the ./src/app/ui/articles.tsx file, add the code below:
1
2
3
// Path: ./src/app/ui/articles.tsx
"use client"; // This is a client-side componentThe "use client" directive will tell Next.js to render a component on the client-side instead of the default server directive.
use HookImport the use hook that will read the streamed data in your client component:
1
2
3
// Path: ./src/app/ui/articles.tsx
import { use } from "react";Image component and Allow ImagesImport the Image component, which provides automatic image optimization.
1
2
3
// Path: ./src/app/page.tsx
import Image from "next/image";Since you are working on a development environment on localhost, you have to configure Next.js to allow images.
Navigate to ./next.config.ts and add the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Path: ./next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "http",
hostname: "localhost",
port: "1337",
pathname: "/**",
},
],
dangerouslyAllowLocalIP: true,
},
};
export default nextConfig;With the dangerouslyAllowLocalIP, Next.js will now allow the Image Optimization API to process images from localhost.
The last step is to:
use hook to read the promise. And lastly, 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Path: ./src/app/ui/articles.tsx
export default function Articles({ articles }: { articles: Promise<any> }) {
const allArticles = use(articles);
// Format date of articles
const formatDate = (date: Date) => {
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "2-digit",
day: "2-digit",
};
return new Date(date).toLocaleDateString("en-US", options);
};
return (
<ul>
<div>
<h2 className="text-2xl font-semibold mb-6">Articles</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{allArticles.data.map((article) => (
<article
key={article.id}
className="bg-white shadow-md rounded-lg overflow-hidden"
>
<Image
className="w-full h-48 object-cover"
src={process.env.NEXT_PUBLIC_STRAPI_URL + article.cover.url}
alt={article.title}
width={180}
height={38}
priority
/>
<div className="p-4">
<h3 className="text-lg font-bold mb-2">{article.title}</h3>
<p className="text-gray-600 mb-4">{article.content}</p>
<p className="text-sm text-gray-500">
Published: {formatDate(article.publishedAt)}
</p>
</div>
</article>
))}
</div>
</div>
</ul>
);
}When you refresh your page, this is what you should see:
As shown above, the fallback component will be shown while the promise is being resolved.
You can find the complete code for each of the example projects in this Github repo.
For specific examples, here are their branches:
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours Monday through Friday at 12:30 pm - 1:30 pm CST: Strapi Discord Open Office Hours
For more details, visit the Strapi documentation and Next.js documentation.