How to integrate Flotiq with a Next.js application? Learn how to fetch data using a personalized SDK, configure caching, and implement draft mode for real-time previews. In this article, we also cover deploying your application on Vercel and ensuring it's ready for production.
Michał Chrząszcz is a Technical Leader at Flotiq, where he blends technical precision with creative problem-solving. Holding a Master’s degree in Computer Science, Michał guides our team in building robust web, intranet and mobile solutions. Based in the charming town of Siechnice, he is an active member of his local community and runs a popular online portal dedicated to connecting and informing residents.
In this article, you will learn how to create a Next.js application integrated with Flotiq. We will describe how to conveniently fetch data using a personalized SDK, configure caching, and utilize draft mode for previewing changes. Finally, we will publish the application on the Vercel platform.
The example application code can be found on GitHub.
Before diving into the article, make sure that:
In a few simple steps, we will create a real-world News page listing current news.
First, we need to create Content Type Definitions for our project. In the proposed example, this will be the Project data type.
1. Log in to the Flotiq panel.
2. Create a Content Type Definition corresponding to our project.
3. Use the Slug plugin, to simplify content entry. This will automatically generate a slug from a specified field.
4. Enter sample data into Flotiq. A few sample entries will suffice:
The first important step is behind us. We now know what we will store in the system and can start adding content. Now it's time to prepare the application based on your model and data.
Navigate to the desired directory and run the following command to create a new Next.js application:
npx create-next-app@latest
We will create a project using the App Router, based on TypeScript, and utilizing Tailwind CSS as the CSS framework. Here is the complete configuration:
Navigate to the application directory and run the application
npm run dev
At http://localhost:3000, you will see the default Next.js homepage.
Now, it's time to add content from Flotiq.
Data from Flotiq can be fetched in various ways - using REST API or GraphQL. Regardless of the approach taken, to communicate with Flotiq, we need to save the API Key in the application, which gives access to your data in Flotiq.
The API Key is available in the Flotiq panel, under the "API Keys" tab. Copy the "Read and write API KEY".
You can generate a key that includes only the necessary elements, e.g., the "Project" Content Type for reading. If the application in the future allows writing, such as comments, you can generate a key with the appropriate permissions.
—
Create a .env.local file and paste the value copied from Flotiq into it.
FLOTIQ_API_KEY=___YOUR_READ_ONLY_API_KEY___
In projects built with TypeScript, we recommend using the personalized Flotiq SDK. It wraps the REST API, providing convenient access to data tailored to your data model, including data types and intelligent code completion.
Download the Flotiq SDK for your project:
npx flotiq-codegen-ts@latest
Executing the command will download the SDK based on your Content Type Definitions. Note that the script used the previously pasted key from the .env.local file. A directory named flotiqApi has been created in your application directory.
Remember, when you change the Content Type Definition settings in Flotiq (e.g., add a new type or change a field in an existing one), you need to run the command again: npx flotiq-codegen-ts.
—
Now let's dive into how to use the downloaded SDK. For the sake of organization, we will save the communication logic with Flotiq in a separate file /src/lib/api.ts.
Create a file /src/lib/api.ts where we will place the basic requests needed to complete the project:
import { FlotiqApi, Project, ProjectList } from "../../flotiqApi/src";
type ProjectFilter = {
slug?: {
type: string,
filter: string
}
}
export async function fetchAllProjects(): Promise<ProjectList> {
const flotiq = new FlotiqApi(process.env.FLOTIQ_API_KEY);
const filters: ProjectFilter = {};
return flotiq.ProjectAPI.list({
limit: 100,
filters: JSON.stringify(filters)
});
}
export async function fetchProjectBySlug(slug: string): Promise<Project | undefined> {
const flotiq = new FlotiqApi(process.env.FLOTIQ_API_KEY);
const filters: ProjectFilter = {
slug: {
type: 'equals',
filter: slug,
}
};
const projects = await flotiq.ProjectAPI.list({
limit: 1,
filters: JSON.stringify(filters)
});
return projects.data?.[0];
}
Note the following:
Notice that in the code, we can use types such as ProjectList or Project, making it more convenient to work with data stored in Flotiq.
Now we are ready to display content from Flotiq.
We have the data model, test entries, the application skeleton, and a convenient way to connect to Flotiq. Now let's move on to displaying content. In the example application, we will list Projects and allow displaying the details of a single Project.
1. Prepare app configuration to display images from Flotiq host. Thanks to that, we can utilize the next/image component.
Update the file nextjs.config.ts to containg the following configuration in the images key:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'api.flotiq.com',
port: '',
pathname: '/**',
},
],
},
};
export default nextConfig;
Note: the change may require rerunning the command yarn dev.
2. Replace the example homepage code defined in /app/page.tsx.
import { fetchAllProjects} from "@/lib/api";
import { Project } from "../../flotiqApi/src";
import Image from "next/image";
import Link from "next/link";
export default async function Home() {
const projects = await fetchAllProjects();
return (
<main className="max-w-6xl mx-auto px-4 py-10">
<h1 className="text-2xl font-bold mb-8 border-b-2 border-gray-600">Our Projects</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{
projects.data?.map((project: Project) => (
<article key={project.id}>
<Link href={`/projects/${project.slug}`}>
<h2 className="text-xl font-semibold mb-2">{project.name}</h2>
<Image
src={`https://api.flotiq.com${project.photos?.[0].url}`}
alt={project.name}
className="w-full h-auto rounded"
width={400}
height={300}
/>
</Link>
</article>
))
}
</div>
</main>
);
}
Note the following:
Check how your application looks. Go to http://localhost:3000 and see the results. The homepage should look like this:
As you have probably noticed, the homepage contains a list of projects and a Link component that will lead to the page of a single Project. In this step, we will define routing and display the description of a single Project at the /projects/[slug] address.
1. Add the Tailwind @tailwindcss/typography plugin
The content of one of the fields we want to display - the description field - will be in HTML format, generated by a Rich Text Editor. To correctly handle the HTML tags (e.g., h1, h2, <strong>), we need to extend Tailwind with the @tailwindcss/typography plugin. Otherwise, they will not be styled, as Tailwind relies solely on classes, not HTML tags.
Add the plugin:
yarn add @tailwindcss/typography
Enable the plugin in the tailwind.config.ts file:
// ...other tailwind config
plugins: [
require('@tailwindcss/typography'),
]
2. Create a single project page /app/projects/[slug]/page.tsx.
According to the Dynamic Route convention in NextJS, the page.tsx file placed in the [slug] directory will be responsible for displaying the content of pages where [slug] will be replaced by possible values from the browser address.
Here is the source code for a single project page:
import { fetchAllProjects, fetchProjectBySlug } from "@/lib/api";
import type { Project, ProjectList } from "../../../../flotiqApi/src";
import Image from "next/image";
import { notFound } from "next/navigation";
import Link from "next/link";
export async function generateStaticParams() {
const allProjects: ProjectList = await fetchAllProjects();
return allProjects.data?.map((project: Project) => ({
slug: project.slug,
})) || [];
}
interface ProjectProps {
params: {
slug: string
};
}
export default async function Project(props: ProjectProps) {
const project = await fetchProjectBySlug(props.params.slug);
if (!project) {
notFound();
}
return (
<main className="max-w-6xl mx-auto px-4 py-10">
<h1 className="text-2xl font-bold mb-8 border-b-2 border-gray-600">{project.name}</h1>
<div className="grid grid-cols-2 gap-6">
<div>
<Image
src={`https://api.flotiq.com${project.photos?.[0].url}`}
alt={project.name}
className="w-full h-auto rounded"
width={400}
height={300}
/>
</div>
<div>
<div
dangerouslySetInnerHTML={{__html: project.description ?? ''}}
className="prose prose-invert"
/>
</div>
<Link href="/">Back</Link>
</div>
</main>
);
}
Note the following:
You just need to navigate to the specific project page, in our case http://localhost:3000/projects/smart-home-automation-system to display the finished page:
Currently, we have a page with a list of projects and a detailed project page, both optimized for using React Server Components and static rendering. This provides exceptional application speed; however, its content is updated only during the build process.
Create a production-optimized application by running the command:
yarn build
Next, run the application:
yarn start
Go to http://localhost:3000/ and see how fast the application runs. What happens, however, when we change the content in Flotiq? With this approach, the page will not update - we must rebuild the project each time using yarn build.
To make our page respond to changes in Flotiq, we will prepare an On-demand Revalidation mechanism.
To make our page behave dynamically, responding to events from Flotiq, we will prepare an On-demand Revalidation strategy in two steps:
Our application will include an additional endpoint, for example, /api/revalidate, which, when queried, will clear the cache within the specified range - for given paths. We will prepare a route handler for this purpose, using the revalidatePath method.
Add a key to the .env.local file that we will use to secure the cache endpoint:
REVALIDATE_KEY=secret_key
Create file /src/app/api/revalidate/route.ts, in which we will handle /api/revalidate
import { revalidatePath } from 'next/cache'
import { NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const providedKey = request.headers.get('x-revalidate-key');
const path = request.nextUrl.searchParams.get('path') ?? '/'
if (providedKey !== process.env.REVALIDATE_KEY) {
return Response.json({ message: 'Invalid revalidate key'}, { status: 401 })
}
revalidatePath(path, 'page');
return Response.json({ path: path, now: Date.now() });
}
Note the following:
Let's see in practice how the revalidate function works:
We have just used the On-demand revalidation mechanism. Thanks to this, updating the content does not require rebuilding the page, only an additional request to the API.
In the next section, we will automate the process using Flotiq Webhooks.
To automate the revalidation process, simply add the appropriate webhooks to Flotiq that will behave like the example CURL requests mentioned in the section above.
To use webhooks, the service must be in a publicly accessible environment. In the examples below, replace the values [APP_PUBLIC_URL] once you publish the service, e.g., on Vercel, as described in the final step of the article.
To use webhooks with a local application (running on localhost), you can also use ngrok to make your local application accessible from the outside.—
We need to add two webhooks that will execute after the change in Content Type Definition Project.
Create the first webhook 'Revalidate homepage cache', providing the following values:
Create another webhook ‘Revalidate project pages cache’, providing the following values:
Summing up, our webhook list should look like this:
To make sure that a given webhook works, you can check its execution log available in the webhook’s edit view.
Remember, for the webhook to execute, your application must be accessible from the outside. This requires either publishing the application or using services like ngrok.
—
Our Next.js application reacts to changes in objects within Flotiq. This ensures that the content fetched from Flotiq, despite using static generation for performance optimization, will always be up-to-date.
In the next stage, we will use another important feature of Next.js - draft mode.
You probably noticed that our Content Type Definition "Project" has a "Status" field, which can be set to "Draft" or "Public". However, we haven't used this field anywhere before.
Our intention is to allow Content Editors to work on content without publishing it. Hence the need to mark some content as "Draft".
The result of this step will be the extension of Flotiq with a "Preview page" button, allowing the preview of pages with draft status. The application in this mode will be generated dynamically, enabling real-time tracking of changes.
We will extend our templates to support Draft Mode, and requests to Flotiq will include the appropriate filters indicating the status in which to display the data.
1. Add an environment variable to the .env.local file, which will be used to secure the preview mode endpoint:
DRAFT_MODE_KEY=secret_key
2. Add Draft Mode information to the homepage template. Thanks to this, the application will know in which mode it is operating.
// src/app/page.tsx
import { draftMode } from "next/headers";
// ...
export default async function Home()
const { isEnabled } = draftMode();
const projects = await fetchAllProjects(isEnabled);
// the rest of current file
Note that we pass the isEnabled information to our function fetchAllProjects.
3. Similarly, add Draft Mode information to the project page template.
// src/app/projects/[slug]/page.tsx
import { draftMode } from "next/headers";
// ...
export default async function Project(props: ProjectProps) {
const { isEnabled } = draftMode();
const project = await fetchProjectBySlug(props.params.slug, isEnabled);
// the rest of current file
4. Let's extend the methods in our src/lib/api.ts file responsible for communicating with Flotiq. Replace the file with the one containing the handling of the draftMode parameter.
// src/lib/api.ts
import { FlotiqApi, Project, ProjectList } from "../../flotiqApi/src";
type ProjectFilter = {
slug?: {
type: string,
filter: string
},
// Added status filter type
status?: {
type: string,
filter: "draft"|"public"
}
}
export async function fetchAllProjects(draftMode?: boolean): Promise<ProjectList> {
const flotiq = new FlotiqApi(process.env.FLOTIQ_API_KEY)
const filters: ProjectFilter = {};
// Added filter status = public, if not in draft mode
if(!draftMode) {
filters.status = {
type: 'equals',
filter: 'public'
}
}
return flotiq.ProjectAPI.list({
limit: 100,
filters: JSON.stringify(filters)
})
}
export async function fetchProjectBySlug(slug: string, draftMode?: boolean): Promise<Project | undefined> {
const flotiq = new FlotiqApi(process.env.FLOTIQ_API_KEY);
const filters: ProjectFilter = {
slug: {
type: 'equals',
filter: slug,
}
}
// Added filter status = public, if not in draft mode
if (!draftMode) {
filters.status = {
type: 'equals',
filter: 'public'
}
}
const projects = await flotiq.ProjectAPI.list({
limit: 1,
filters: JSON.stringify(filters)
});
return projects.data?.[0];
}
Note the following:
Note that on the project’s homepage, at http://localhost:3000, only projects marked with the "public" flag in Flotiq are visible.
Now, we will add an API endpoint to instruct the application to also show content with draft status.
Add a handler to process the path /api/draft?slug=/&key=secret_key. In accordance with the Next.js convention, place it in the /app/api/draft/route.ts file:
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret');
const disable = searchParams.get('disable');
const slug = searchParams.get('slug') ?? '/';
// Turn on draft mode
if (!disable) {
if (!secret || secret !== process.env.DRAFT_MODE_KEY) {
return new Response("Missing or invalid secret", { status: 401 });
}
draftMode().enable();
redirect(slug);
}
// Turn off draft mode
draftMode().disable();
redirect(slug);
}
Note the following:
Draft Mode handling is ready! By navigating to http://localhost:3000/api/draft?secret=secret_key, you should see all posts on the homepage, including those with the draft status.
In the next step, we will propose a minor improvement to make using Draft Mode easier.
To improve Draft Mode on the application side, we can add a component informing you that you are in draft mode. Additionally, we will add a link to conveniently exit preview mode.
To add the DraftModeToolbar component, add the src/app/ui/DraftModeToolbar.tsx file.
'use client'
import { usePathname } from "next/navigation";
export default function DraftModeToolbar() {
const pathName = usePathname();
return (
<div className="fixed top-0 right-0 py-2 px-4 bg-red-500">
<p>Draft mode enabled</p>
<a href={`/api/draft/?disable=true&slug=${pathName}`}>Disable</a>
</div>
)
}
Note the following:
We will use this component in the project layout, in the src/app/layout.tsx file.
// src/app/layout.tsx
// ...
import { draftMode } from "next/headers";
import DraftModeToolbar from "@/app/ui/DraftModeToolbar";
// ...
export default function RootLayout({children}: Readonly<{children: React.ReactNode;}>) {
const { isEnabled } = draftMode();
return (
<html lang="en">
<body className={inter.className}>
{ isEnabled && <DraftModeToolbar/> }
{ children }
</body>
</html>
);
}
Note the following:
After these changes, when you are in Draft Mode, you will see a convenient notification with a link to exit Draft Mode in the top right corner of the application.
The final step is to integrate the Flotiq interface with Draft Mode.
To make working in Flotiq even more convenient, we will extend Flotiq with additional buttons to open the project preview. Here is what your project editing form will look like:
1. In your Flotiq account, add the “Custom Links” plugin.
2. Configure the plugin to display the “Preview page” and “Open published page” buttons. Click “Manage” on the plugin list and fill it out with the following values.
Preview page:
Open published page:
Here’s what the configuration should look like:
This way, we’ve extended the Flotiq interface. Check how the buttons behave in the project editing view. Remember, if you change the application's address, add new buttons or update the button URLs.
Congratulations! We’ve created a complete application - from the template, through fetching content from Flotiq, using cache, and up to Draft Mode. Now it's time to share it with the world. In the final step, we will describe deployment to the Vercel platform.
Vercel is an excellent choice for deploying a Next.js application for several reasons: native integration with Next.js, simple configuration, a global CDN network ensuring fast page loading, support for serverless functions, automatic SSL, and easy domain management.
The deployment process is intuitive: connect your repository from GitHub, GitLab, or Bitbucket, and Vercel will automatically build the application after each push.
To start working with Vercel, it is most convenient to create a repository on GitHub, Bitbucket, or GitLab. In our article, we will use the first one (GitHub).
Create a GitHub repository and upload the project files:
git add .
git commit -m "Next.js with Flotiq - example app"
git remote add origin https://github.com/[user]/[repository].git
git push origin main
Next, create a Vercel account or log in to an existing account. Select importing the repository from GitHub, choosing your account and the repository with the Next.js project.
After entering the configuration view, don’t forget to enter the environment variables corresponding to those in your .env.local file.
Click the “Deploy” button. The operation may take a few minutes.
Once the build process is complete, the site will be available, and you can find its address on the Vercel dashboard.
From now on, the site is available to the public.
The final step will be to update the addresses of webhooks and links in Flotiq. They should include the domain set on Vercel. For webhooks, replace the previous placeholders APP_PUBLIC_URL with the actual values.
For plugins, replace the previous http://localhost:3000 with the domain set on Vercel.
In this article, we discussed the steps needed to create a Next.js application connected to Flotiq. We presented how to fetch data using a personalized SDK, configure caching, and use draft mode to preview changes. Finally, we deployed the application on the Vercel platform.
The components used—SDKs, webhooks, and plugins—demonstrate how to conveniently utilize Flotiq and adapt it to various types of projects.
The code for the application prepared for the article is available in a public GitHub repository.