- Published on
Using Strapi with Next.js to generate dynamic web pages on the fly
In this blog post, we will explore how to leverage Strapi and Next.js to create dynamic web pages with ease. We will discuss step-by-step instructions to set up the integration. You can find the source code of this demo at the bottom.
Table of Contents
- Introduction
- Setting Up Next.js
- Adding UI Components with TailwindCSS
- Creating a Dynamic Route Page
- Introducing Strapi as a Headless CMS
- Defining Components and Sections in Strapi
- Creating the Collection Type
- Managing Content with Strapi's Content Manager
- Testing the API Response
- Integrating Strapi API in Next.js
- References
- Conclusion
Introduction
When developing a website, we often need to fetch data from backend APIs and pass it to UI components to populate the content dynamically. However, managing the UI components and generating pages on-the-fly typically requires custom backend development and a dedicated dashboard. In this blog post, we will explore an alternative approach using Strapi, a headless CMS, which simplifies this process.
We will build our website using Next.js, a popular React framework, and leverage TailwindCSS for creating reusable UI components. By integrating Strapi into our architecture, we can easily manage the UI components and compose dynamic web pages.
Setting Up Next.js
To begin, make sure you have Next.js installed on your system. If not, you can follow the installation steps provided on the official Next.js website. Once installed, create a new Next.js application by running the following command:
npx create-next-app@latest
Start your Next.js application using the command:
npm run dev
Ensure that the application is running by checking the terminal for a message like:
ready started server on 0.0.0.0:3000, url: http://localhost:3000
Adding UI Components with TailwindCSS
To quickly get started, we will use the UI components available on the Tailwind UI website. Copy the React source code for the desired components. Create individual files for each component under the src/components directory in your Next.js project.
For this demo, we can copy the following components.
- Hero Section
- Stats
- CTA Section
- Pricing Section
- Testimonials
To use these Tailwind UI components, we need to first install the required npm packages by running the following command:
npm i @headlessui/react @heroicons/react
Creating a Dynamic Route Page
To generate dynamic pages, we need to create a dynamic route page in Next.js. Instead of adding this page to the root level, we'll create a sub-directory named shop inside the pages directory. Within the shop directory, create the dynamic route page named [slug].tsx
and import the UI components we previously created.
import HeroSection from "@/components/HeroSection";
import StatSection from "@/components/StatSection";
import CtaSection from "@/components/CtaSection";
import PricingSection from "@/components/PricingSection";
import TestimonialSection from "@/components/TestimonialSection";
export default function Shop() {
return (
<>
<HeroSection />
<StatSection />
<CtaSection />
<PricingSection />
<TestimonialSection />
</>
);
}
This page will render all the UI components when you visit a specific slug URL, such as http://localhost:3000/shop/features
Introducing Strapi as a Headless CMS
To simplify the management of UI components and content, we will integrate Strapi as a headless CMS. Begin by installing Strapi on your system. You can find the installation steps on the Strapi website.
Once Strapi is installed, create a new Strapi project by running the following command in your terminal:
npx create-strapi-app my-project --quickstart
This command will create a new Strapi project with a pre-defined content structure and a SQLite database for local development.
Defining Components and Sections in Strapi
In Strapi, we can define the UI components and sections that we want to use on our dynamic pages.
Access the Strapi admin panel by visiting
http://localhost:1337/admin
in your browser.Go to the "Content-Type Builder" section and click on the "Create new component".
Define each UI component or section you want to manage. For example, if you have a HeroSection component, create a Collection Type named "HeroSection" with fields such as title, subtitle, and image.
Creating the Collection Type
Next, we need to create a Collection Type that represents the dynamic pages we want to generate. In our case, it will be a "Shop" Collection Type.
In the Strapi admin panel, go to the "Content-Type Builder" section.
Click on the "Create new collection type".
Name the Collection Type as "Shop" and define the necessary fields. For example, you can include a "slug" field to store the URL slug for the page.
Add a "Dynamic zone" field named "sections" and include the sections we created. This field will allow us to select and arrange the UI components and sections on the page.
Managing Content with Strapi's Content Manager
Now that we have our Collection Types defined, we can start managing the content for our dynamic pages.
Go to the "Content Manager" section in the Strapi admin panel.
Click on the "Shop" content type to start adding new pages.
Create a new entry by clicking on the "Create new entry" button.
Fill in the necessary fields, such as the slug and the sections with the desired UI.
Save the entry.
Testing the API Response
Once we have entered the data through the Content Manager, we can cross-check the API response. To do this, we first need to add an API token.
Go to Settings, then API Tokens, and click on Create new API Token. Provide a name and description for identification. For this demo purpose, you can select any duration from the drop-down menu. Choose the Read-Only option from the Token type drop-down. Once the token is created, make sure to copy it and save it for later use.
If you have the Postman tool, you can check whether the API is working correctly. Add a new tab, enter your API URL http://localhost:1337/api/shops/1
. Here shops indicates our collection and 1 represents the id of the Shop collection entry.
Next, we need to provide authentication details. Select Bearer Token from the Authentication tab and paste the token we copied earlier into the Token input field. If everything is correct, you should be able to see the API response.
Now, let's modify the API request to retrieve the content using a slug instead of the collection entry ID. We can use the following API: http://localhost:1337/api/shops?filters[slug]=features
However, by default, Strapi's REST API does not populate any relations, media fields, components, or dynamic zones in the response. You can find more information about this in Strapi's Dev-Docs. To simplify the process, we will use the strapi-plugin-populate-deep package. After installing the package and restarting Strapi, you will receive all the content in the API response when calling this API: http://localhost:1337/api/shops?filters[slug]=features&populate=deep
Integrating Strapi API in Next.js
Finally, we can render the dynamic pages in our Next.js application based on the data retrieved from Strapi. Here is the final source code of StatSection
component.
type Stats = {
id: number;
title:string;
subTitle:string;
}
type Props = {
statsList: Stats[];
}
export default function StatSection(props:Props) {
return (
<div className="bg-white py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-3">
{props.statsList.map((stats:Stats) => (
<div key={stats.id} className="mx-auto flex max-w-xs flex-col gap-y-4">
<dt className="text-base leading-7 text-gray-600">{stats.subTitle}</dt>
<dd className="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl">
{stats.title}
</dd>
</div>
))}
</dl>
</div>
</div>
)
}
and the updated source code of [slug].tsx
file to render required components dynamically based on the Strapi API.
import { GetServerSidePropsContext } from "next/types";
import dynamic from "next/dynamic";
const HeroSection = dynamic(() => import("@/components/HeroSection"));
const CtaSection = dynamic(() => import("@/components/CtaSection"));
const PricingSection = dynamic(() => import("@/components/PricingSection"));
const StatSection = dynamic(() => import("@/components/StatSection"));
const TestimonialSection = dynamic(() => import("@/components/TestimonialSection"));
type Props = {
sections: any;
}
const getSectionsComponent = ({ id, __component, ...rest }: any) => {
let SectionsComponent;
if (__component === "sections.hero"){
SectionsComponent = HeroSection;
}
else if (__component === "sections.pricing"){
SectionsComponent = PricingSection;
}
else if (__component === "sections.stats"){
SectionsComponent = StatSection;
}
else if (__component === "sections.cta"){
SectionsComponent = CtaSection;
}
else if (__component === "sections.testimonials"){
SectionsComponent = TestimonialSection;
}
return SectionsComponent ? <SectionsComponent key={`index-${__component}-${id}`} {...rest} /> : null;
};
export default function Shop({sections}: Props) {
return (
<>
{sections.map(getSectionsComponent)}
</>
);
}
export const getServerSideProps = async (ctx:GetServerSidePropsContext) => {
const slug = ctx.query.slug;
const res = await fetch(
`${process.env.CMS_BASE_URL}/api/shops?filters[slug]=${slug}&populate=deep`,
{
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + process.env.CMS_TOKEN
}
}
);
if (!res.ok) {
throw new Error('Failed to fetch data');
}
const result = await res.json();
if (result.data.length === 0) {
return {
notFound: true
}
}
return {
props: {
sections: result.data[0].attributes.sections
}
}
}
If you noticed, I've replaced the import statements with dynamic imports. This is because if we directly import all the components, they will be bundled together in a single file. As we start adding more dynamic pages with different combinations of components, the included bundle file will contain a lot of unused code for those specific pages.
To avoid this issue, we can use dynamic imports. We can keep direct imports for the components that we know for sure need to be included in all pages. By using dynamic imports, we can load the necessary components only when they are actually required, reducing the size of the bundled files and improving the overall performance of our application.
With this setup, you now have a scalable and flexible system for managing and rendering dynamic pages in your Next.js application using Strapi as the headless CMS. You can easily create and update pages, components, and sections in Strapi's intuitive admin panel and have them rendered dynamically in your Next.js frontend.
In the following github repositories, you can find the source code of both Next.js and Strapi applications.
https://github.com/supekarnikhil/demo-nextjs-app
https://github.com/supekarnikhil/demo-strapi-cms
References
For this demo, I've referred the following blog post. I encourage you to explore it to gain a deeper understanding of the topic. https://strapi.io/blog/how-to-create-pages-on-the-fly-with-dynamic-zone
Conclusion
In this blog post, we explored how to leverage the power of Next.js and Strapi to build dynamic pages in a headless CMS architecture. By following the steps outlined, you can create a robust system for managing and rendering dynamic pages, allowing for greater flexibility and scalability in your web development projects.
By decoupling the frontend and backend, you can empower your team to work simultaneously on the UI and content management aspects of your application. This separation also enables you to scale your application more efficiently and reuse components across different pages.
Next.js provides the server-side rendering capabilities necessary for performance and SEO benefits, while Strapi offers a user-friendly interface for managing content. Together, they form a powerful combination for building modern, dynamic web applications.
I hope this guide has provided you with valuable insights and a solid foundation for implementing dynamic pages with Next.js and Strapi. Happy coding!