<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Paweł Panowicz</title>
    <description>The latest articles on Forem by Paweł Panowicz (@pawe_panowicz_8aae3e260d).</description>
    <link>https://forem.com/pawe_panowicz_8aae3e260d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2652155%2Feac86408-0184-4440-911a-8dc233837f94.jpg</url>
      <title>Forem: Paweł Panowicz</title>
      <link>https://forem.com/pawe_panowicz_8aae3e260d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pawe_panowicz_8aae3e260d"/>
    <language>en</language>
    <item>
      <title>Best headless CMS for Next.js - Typescript support comparison</title>
      <dc:creator>Paweł Panowicz</dc:creator>
      <pubDate>Wed, 22 Jan 2025 11:29:38 +0000</pubDate>
      <link>https://forem.com/flotiq/best-headless-cms-for-nextjs-typescript-support-comparison-3j14</link>
      <guid>https://forem.com/flotiq/best-headless-cms-for-nextjs-typescript-support-comparison-3j14</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to Best Headless CMS for Next.js - TypeScript Support Comparison
&lt;/h2&gt;

&lt;p&gt;As TypeScript adoption continues to grow in modern web development, developers are increasingly seeking Headless CMS platforms that integrate seamlessly with TypeScript. TypeScript enhances productivity with features like static typing, type inference, and code autocompletion, while reducing errors in development workflows.&lt;/p&gt;

&lt;p&gt;When building with Next.js, choosing a CMS with strong TypeScript support and TypeScript resources can significantly streamline development. In this comparison, we analyzed three popular Headless CMS platforms: Strapi, Contentful, and Flotiq. We examined their strengths and limitations, focusing on developer experience, integration complexity, and maintenance concerns. For each of these solutions - we looked through the official documentation and available tutorials and examples, to find out how to make it work with Typescript.&lt;/p&gt;

&lt;p&gt;Let’s dive into the details!&lt;/p&gt;

&lt;h2&gt;
  
  
  Strapi
&lt;/h2&gt;

&lt;p&gt;Strapi is a popular open-source Headless CMS known for its flexibility and self-hosting capabilities. It provides a REST API and optional GraphQL support, making it a versatile choice for developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Types Setup in Strapi
&lt;/h3&gt;

&lt;p&gt;First you need to create Content-Types which you plan to use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wc2l11idmdmvwr02065.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wc2l11idmdmvwr02065.png" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you define the data model, content editors can immediately start their content management work using the generated forms (this is true only for the Strapi Cloud version). If self-hosting, additional steps are required: you must deploy Strapi to a publicly accessible server and configure appropriate access permissions for editors to interact with the system. This means you need to have devops/dbaAdmin engineers available in your organisation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnwmlbez4rwugh7pg630.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnwmlbez4rwugh7pg630.png" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The development team gets access to the generated REST API, which reflects the data model. Specific endpoints allow access to different types of data, which is natural for developers with experience in RESTful APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl localhost:1337/api/products -H "Authorization:bearer $STRAPI\_TOKEN"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This request queries the products API for all objects and produces a response similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "data": \[
    {
      "id": 1,
      "attributes": {
        "Name": "Test",
        "enabled": true,
        "createdAt": "2023-12-08T08:26:17.828Z",
        "updatedAt": "2025-01-06T18:58:33.421Z",
        "publishedAt": "2025-01-06T18:58:33.420Z",
        "locale": "en",
        "description": null,
        "price": null
      }
    }
  \],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 1
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Typescript support in Strapi
&lt;/h3&gt;

&lt;p&gt;The standard way of accessing the Strapi API in a Typescript project, as shown in Strapi tutorials (see links [&lt;a href="https://strapi.io/blog/build-a-blog-with-next-react-js-strapi" rel="noopener noreferrer"&gt;1&lt;/a&gt;], [&lt;a href="https://strapi.io/blog/getting-started-with-next-js-and-strapi-5-beginner-s-guide" rel="noopener noreferrer"&gt;2&lt;/a&gt;], [&lt;a href="https://strapi.io/blog/build-a-developer-blog-with-strapi5-and-nextjs" rel="noopener noreferrer"&gt;3&lt;/a&gt;], [&lt;a href="https://strapi.io/blog/how-to-build-a-to-do-app-using-next-js-and-strapi" rel="noopener noreferrer"&gt;4&lt;/a&gt;], [&lt;a href="https://strapi.io/blog/improve-your-frontend-experience-with-strapi-types-and-type-script" rel="noopener noreferrer"&gt;5&lt;/a&gt;]), requires manual setup of interfaces for the IDE to understand the data model, so you can utilize the advantages offered by Typescript:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build a blog with Next (React.js) and Strapi&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48eycyi6aqcaerzjqfec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48eycyi6aqcaerzjqfec.png" width="800" height="1238"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Getting Started With Next.js and Strapi 5: beginner's guide&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlvksr7qcgbi6c60vef0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlvksr7qcgbi6c60vef0.png" width="800" height="842"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build a Developer Blog with Strapi 5 and Next.js&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpwhpdooloapjjhjc3ti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpwhpdooloapjjhjc3ti.png" width="800" height="750"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How to build a To-do App using Next.js and Strapi&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlh0qz3n0o9zpsath8j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlh0qz3n0o9zpsath8j7.png" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Improve Your Frontend Experience With Strapi Types And TypeScript&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tu0bkda60avhm0wy975.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tu0bkda60avhm0wy975.png" width="800" height="880"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you define these interfaces, the IDE will start suggesting proper attributes and Typescript will verify type correctness:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv1k2hdzg9sjmcxangrjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv1k2hdzg9sjmcxangrjq.png" width="800" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8wpbonrzesf7sfjk6dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8wpbonrzesf7sfjk6dt.png" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, hand-crafting these interfaces is error-prone, tedious, and not suited for long-term maintenance as any change in the data model will have to be manually adjusted in the interfaces.&lt;/p&gt;

&lt;p&gt;For those with direct access to the Strapi backend project, you can access type definitions generated by Strapi. Deep down in the file, you can find interfaces for all content types. You can import these files into the client/frontend project, but there are some caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The file might need tidying up as it contains many interfaces that will not be used (for internal Strapi types).&lt;/li&gt;
&lt;li&gt;  When you copy the file into the frontend, it will get out of sync with the data model once changes are made to the content type structure.&lt;/li&gt;
&lt;li&gt;  Typescript interfaces are generated in development mode, so someone working on the Strapi backend side of things has to generate them and pass them to you. While this may be a problem in larger, enterprise projects, it's likely not an issue in smaller builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlrbtdrctd0tph5gtxi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlrbtdrctd0tph5gtxi8.png" width="800" height="876"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, you can finally utilize Typescript in the client application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatxp0ax1se0bcs5nnzwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatxp0ax1se0bcs5nnzwx.png" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note how the IDE autocompletes the property names available in the Product content type.&lt;/p&gt;

&lt;p&gt;Unfortunately, some additional concerns arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The Product content type definition is exported with a rather odd name - ApiProductProduct.&lt;/li&gt;
&lt;li&gt;  With the default setup, the IDE might not be able to properly interpret the types on all attributes (in the screenshot above, the description field is interpreted as any, which is a sort of failover for missing type definition in Typescript).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Most tutorials and code samples for Strapi do not make full use of Typescript or require manual crafting of interfaces. Creating interfaces by hand is not acceptable for professional projects built with long-term support in mind, but it may be fine for small, pet projects under full control.&lt;/li&gt;
&lt;li&gt;  If you can copy generated type definitions from the Strapi backend to client applications, it provides a simple way to get (limited) benefits of Typescript in the client app.&lt;/li&gt;
&lt;li&gt;  Manual synchronization of type definitions from the backend will be required whenever a change is made in the data model. This may require a complicated human-based process or automation that may also create problems.&lt;/li&gt;
&lt;li&gt;  Type definitions imported in a client app may not provide full support for typing on all object properties, and you have to accept that the *.d.ts files will contain definitions for Strapi internals, as well as the fact that type names may not be very friendly (the ApiProductProduct case).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  One more thing: What if you do not have access to the generated type definitions files?
&lt;/h4&gt;

&lt;p&gt;This can easily happen when the project is in maintenance mode and updates are needed without access to the development team. It's also common in large enterprise builds, where separate teams are responsible for maintaining the CMS backend and building client frontend applications.&lt;/p&gt;

&lt;p&gt;The Strapi community understands this problem and has come up with solutions based on either OpenAPI or GraphQL (both carry type information), but these solutions are not exactly out-of-the-box or trivial to use.&lt;/p&gt;

&lt;p&gt;The OpenAPI approach has been thoroughly described on the &lt;a href="https://strapi.io/blog/type-safe-fetch-with-next-js-strapi-and-open-api" rel="noopener noreferrer"&gt;Strapi Blog&lt;/a&gt; and, although quite complex, it offers a good and standards-based solution to the problem.&lt;/p&gt;

&lt;p&gt;However, the GraphQL-based setup (which should be simpler) is only referenced in a &lt;a href="https://forum.strapi.io/t/graphql-frontend-schema-types/1432/2" rel="noopener noreferrer"&gt;Strapi forum thread&lt;/a&gt; from 2021:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxdmwrg3we627igqybgu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxdmwrg3we627igqybgu.png" width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But since the &lt;a href="https://github.com/dotansimha/graphql-code-generator" rel="noopener noreferrer"&gt;dotansimha/graphql-code-generator&lt;/a&gt; library is hugely popular, this approach is likely to be successful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contentful
&lt;/h2&gt;

&lt;p&gt;Contentful is a cloud-hosted Headless CMS, known for its intuitive content editor interface and robust API options (GraphQL and REST).&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Type Setup in Contentful
&lt;/h3&gt;

&lt;p&gt;In order to compare with Strapi and Flotiq - we start with the same content-type built in Contentful:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfflxx6oim4n6hojyd4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfflxx6oim4n6hojyd4o.png" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you define the model, content editors get a graphical interface to enter content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cf79z193cfi29c681nd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cf79z193cfi29c681nd.png" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Developers have access to generic Contentful GraphQL and REST APIs, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0hvjf4vohes32n6nwg3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0hvjf4vohes32n6nwg3.png" width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzhdf0km7cswonwb84hk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzhdf0km7cswonwb84hk.png" width="800" height="809"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's easy to see that the API described in Contentful docs is not very friendly to use. Strapi and Flotiq have an easier way of accessing content, where API endpoints reflect the content types which are created in your account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typescript support in Contentful
&lt;/h3&gt;

&lt;p&gt;Contentful publishes an interesting blog post, highest ranking for "contentful nextjs typescript", &lt;a href="https://www.contentful.com/blog/typescript-nextjs/" rel="noopener noreferrer"&gt;How to use TypeScript in your Next.js project&lt;/a&gt;. Unfortunately, the post seems mainly SEO-oriented and only &lt;a href="https://www.contentful.com/blog/typescript-nextjs/#richtxt-next-js-typescript-and-contentful-a-powerful-combination-for-fast-reliable-apps" rel="noopener noreferrer"&gt;briefly mentions&lt;/a&gt; how Typescript, Contentful, and Next.js can improve developer experience:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhika96cbqdkhlurqu5fi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhika96cbqdkhlurqu5fi.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not very useful.&lt;/p&gt;

&lt;p&gt;The official &lt;a href="https://github.com/vercel/next.js/tree/canary/examples/cms-contentful" rel="noopener noreferrer"&gt;Contentful + next.js starter&lt;/a&gt; does not seem to provide any support for Typescript on the content management side of things, either. Furthermore, it uses hardcoded GraphQL queries and content models to fetch the data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fkabptah4mega5f8mlk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fkabptah4mega5f8mlk.png" width="800" height="995"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As indicated by the wide use of the any TS type, it does not support any integration with the IDE:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhni8eyjgiefz6nh53n7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhni8eyjgiefz6nh53n7.png" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://maxschmitt.me/posts/nextjs-contentful-typescript" rel="noopener noreferrer"&gt;blog post by Max Schmitt&lt;/a&gt; is an improvement, as it shows how adding hand-crafted Typescript interfaces to the code can provide TS support:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk14qh4kksa038j3pjbjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk14qh4kksa038j3pjbjr.png" width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.contentful.com/developers/changelog/#stable-release-of-contentfuljs-v10-with-enhanced-typescript-support" rel="noopener noreferrer"&gt;contentful.js v10 announcement&lt;/a&gt; promised improved Typescript support, but manually created interfaces can still be seen in the demo code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbd31z3qhg1ixecpqi5pc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbd31z3qhg1ixecpqi5pc.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, when digging deeper into the contentful.js SDK, the Typescript readme file provides a &lt;a href="https://github.com/contentful/contentful.js/blob/master/TYPESCRIPT.md#generating-type-definitions-for-content-types" rel="noopener noreferrer"&gt;list of 3rd party open-source packages&lt;/a&gt; that can help with type generation.&lt;/p&gt;

&lt;p&gt;Out of 4 packages, only 2 seem noteworthy.&lt;/p&gt;

&lt;h4&gt;
  
  
  intercom/contentful-typescript-codegen
&lt;/h4&gt;

&lt;p&gt;Using the &lt;a href="https://github.com/intercom/contentful-typescript-codegen" rel="noopener noreferrer"&gt;intercom/contentful-typescript-codegen&lt;/a&gt; package (most starred among the 4 packages listed by Contentful) actually yields promising results, including type information on individual fields:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69wznxzhqgy5b85vrp4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69wznxzhqgy5b85vrp4r.png" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, you still need to manually edit the GraphQL query.&lt;/p&gt;

&lt;p&gt;To get around this, you can use the contentful.sdk, which should be able to understand the data model in the Contentful account. Unfortunately, it turns out that types generated from the Intercom package do not support the Skeleton types used by the official Contentful SDK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31ipxuk5vedd4otli6em.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31ipxuk5vedd4otli6em.png" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  contentful-userland/cf-content-types-generator
&lt;/h4&gt;

&lt;p&gt;Although the &lt;a href="https://github.com/contentful-userland/cf-content-types-generator" rel="noopener noreferrer"&gt;contentful-userland/cf-content-types-generator&lt;/a&gt; package seems to be less popular than the Intercom one, it seems to provide better compatibility with the Contentful Typescript SDK.&lt;/p&gt;

&lt;p&gt;After running&lt;/p&gt;

&lt;p&gt;yarn add cf-content-types-generator&lt;/p&gt;

&lt;p&gt;the package is installed and ready to generate types compatible with the current Contentful SDK&lt;/p&gt;

&lt;p&gt;yarn run cf-content-types-generator -s $CONTENTFUL_SPACE_ID -t $CONTENTFUL_MANAGEMENT_ACCESS_TOKEN --out @types --response -X&lt;/p&gt;

&lt;p&gt;This produces a clean set of type definitions, like this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg37vrnsh1drd9gi5phs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg37vrnsh1drd9gi5phs.png" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;… so much better than what was obtained with Strapi.&lt;/p&gt;

&lt;p&gt;With this in place, the IDE is finally ready to work with Typescript and Contentful:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1d9qefyenmzsw337rf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1d9qefyenmzsw337rf.png" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion for Contentful
&lt;/h3&gt;

&lt;p&gt;Contentful offers partial TypeScript support through third-party tools, but the integration process is cumbersome. The basic setup relies on manually crafted GraphQL queries and and hardcoded interfaces, which limits its appeal for TypeScript-driven workflows. On the other hand, when fully setup with the 3rd party tools - Contentful offers a better experience than Strapi, it also allows content editors to start working immediately after the content types are setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flotiq
&lt;/h2&gt;

&lt;p&gt;Flotiq stands out for its simplicity, developer-first design, and comprehensive tools for content editors and developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Type Setup in Flotiq
&lt;/h3&gt;

&lt;p&gt;Adding a content type definition in Flotiq is very similar to the process seen in Contentful or Strapi.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x4jn6oqnw2v7r59jyhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x4jn6oqnw2v7r59jyhk.png" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you define the model, as with any other headless CMS, content editors working with Flotiq can start creating content using an easy-to-use form:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscjk17m8gu083oqlmy13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscjk17m8gu083oqlmy13.png" width="800" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similar to other cloud-hosted systems, like Contentful or Strapi Cloud, Flotiq provides the editors with immediate access to data. Self-hosted systems, in contrast, require a significant (sometimes) effort to deploy and host the system in a way that it’s available to the entire team.&lt;/p&gt;

&lt;p&gt;What makes Flotiq stand out, compared to Contentful and Strapi, is the amount of options and easy-to-use tools it gives to the development team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  API documentation - generated and always up-to-date with your content model&lt;/li&gt;
&lt;li&gt;  SDKs - downloadable for several popular languages&lt;/li&gt;
&lt;li&gt;  OpenAPI support - for easy integration with other systems&lt;/li&gt;
&lt;li&gt;  Scoped APIs - to easily generate subsets of the content API for different purposes&lt;/li&gt;
&lt;li&gt;  Hosted webhooks - for quickly adding extensions without worrying about hosting code&lt;/li&gt;
&lt;li&gt;  Lifecycle webhooks - synchronous webhooks which let developers modify content before it’s persisted in the CMS&lt;/li&gt;
&lt;li&gt;  UI plugins - for extending the look and function of the GUI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find more about these features in the &lt;a href="https://flotiq.com/docs/" rel="noopener noreferrer"&gt;Flotiq developer documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typescript integration in Flotiq
&lt;/h3&gt;

&lt;p&gt;Flotiq’s native support for OpenAPI makes it easy to generate client code for different languages - Java, Python, Dart (for Flutter), Typescript and several more. The Typescript SDK has received much love in the recent months and provides the easiest and most comprehensive method of accessing CMS content among all compared systems.&lt;/p&gt;

&lt;p&gt;Install the &lt;a href="https://github.com/flotiq/flotiq-codegen-ts" rel="noopener noreferrer"&gt;flotiq/flotiq-codegen-ts&lt;/a&gt; sdk&lt;/p&gt;

&lt;p&gt;npx flotiq-codegen-ts generate&lt;/p&gt;

&lt;p&gt;During the installation you will be asked to provide your Flotiq API key and a few seconds later - the types will be generated. The resulting integration is, by far, the easiest to use and provides full support for types on all attributes in your content model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdl6z4b9jx74trofeyk0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdl6z4b9jx74trofeyk0.png" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This level of integration provides developers with the best most convenient access to the data stored in their CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions for Flotiq
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Flotiq’s SDK for Typescript is easier to configure and install than what Contentful or Strapi has to offer&lt;/li&gt;
&lt;li&gt;  Flotiq-codegen-ts supports information about types of attributes, return types on API methods as well as field descriptions (same that content editors see in their forms)&lt;/li&gt;
&lt;li&gt;  Flotiq API is reflecting the content types defined in the system (like Strapi) and does not force developers to learn any system-specific content API (which is what Contentful does)&lt;/li&gt;
&lt;li&gt;  Ease of use and Typescript support make Flotiq a great choice for Next.js projects.&lt;/li&gt;
&lt;li&gt;  Flotiq TypeScript resources and integration information is available here: &lt;a href="https://flotiq.com/docs/Deep-Dives/nextjs-react-typescript-openapi/#flotiq-codegen-ts" rel="noopener noreferrer"&gt;https://flotiq.com/docs/Deep-Dives/nextjs-react-typescript-openapi/#flotiq-codegen-ts&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Choosing the right Headless CMS for your Next.js project depends on your priorities. Here’s a quick summary of TypeScript CMS support with our ranging, from most compatible to lowest compatible:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Flotiq&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Provides &lt;strong&gt;native TypeScript SDK generation&lt;/strong&gt; with flotiq-codegen-ts.&lt;/li&gt;
&lt;li&gt;  Fully typed models ensure complete type safety and IDE autocompletion.&lt;/li&gt;
&lt;li&gt;  Automatically synchronizes with content model changes, requiring no additional configuration.&lt;/li&gt;
&lt;li&gt;  Offers the most streamlined and developer-friendly experience, especially for large or dynamic projects.&lt;/li&gt;
&lt;li&gt;  Is a fully managed product, with a generous free tier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Contentful&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Lacks native TypeScript integration; relies on third-party tools like contentful-typescript-codegen or cf-content-types-generator.&lt;/li&gt;
&lt;li&gt;  Generated types often require manual adjustments to work seamlessly with the SDK.&lt;/li&gt;
&lt;li&gt;  If opting for GraphQL-based approach - it may require hardcoding of queries, reducing flexibility.&lt;/li&gt;
&lt;li&gt;  Has a fully managed free tier (very limited), but the paid tiers have a steep price point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Strapi&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Requires manual creation of TypeScript interfaces or syncing generated definitions from the backend.&lt;/li&gt;
&lt;li&gt;  Limited type coverage; some attributes default to any.&lt;/li&gt;
&lt;li&gt;  Synchronization issues arise when content models change, increasing maintenance overhead.&lt;/li&gt;
&lt;li&gt;  Best suited for small projects or setups where developers have full backend access.&lt;/li&gt;
&lt;li&gt;  Managed offering does not include a free tier, but it can be self-hosted, if you have the expertise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe1ji13uvzxh5s011f1n.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe1ji13uvzxh5s011f1n.jpg" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>typescript</category>
      <category>flotiq</category>
      <category>headlesscms</category>
    </item>
    <item>
      <title>Next.js and Flotiq: Fetching, cache, and draft mode</title>
      <dc:creator>Paweł Panowicz</dc:creator>
      <pubDate>Mon, 20 Jan 2025 09:33:17 +0000</pubDate>
      <link>https://forem.com/flotiq/nextjs-and-flotiq-fetching-cache-and-draft-mode-41d2</link>
      <guid>https://forem.com/flotiq/nextjs-and-flotiq-fetching-cache-and-draft-mode-41d2</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The example application code can be found on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving into the article, make sure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You have a Flotiq account.&lt;/li&gt;
&lt;li&gt;  You have a Vercel account.&lt;/li&gt;
&lt;li&gt;  You have basic knowledge of React.js, TypeScript, and Next.js.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Action Plan
&lt;/h2&gt;

&lt;p&gt;In a few simple steps, we will create a real-world News page listing current news.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Setup content types and data in Flotiq&lt;/li&gt;
&lt;li&gt; Setup the Next.js application&lt;/li&gt;
&lt;li&gt; Connect Flotiq with Next.js using a personalized SDK&lt;/li&gt;
&lt;li&gt; Display a list of articles and a single article&lt;/li&gt;
&lt;li&gt; Build a statically generated application&lt;/li&gt;
&lt;li&gt; Implement the On-demand revalidation mechanism&lt;/li&gt;
&lt;li&gt; Implement Next.js Draft Mode&lt;/li&gt;
&lt;li&gt; Go live! Deployment to Vercel&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Setup Content Types in Flotiq
&lt;/h2&gt;

&lt;p&gt;First, we need to create Content Type Definitions for our project. In the proposed example, this will be the Project data type.&lt;/p&gt;

&lt;p&gt;1. Log in to the Flotiq panel.&lt;br&gt;&lt;br&gt;
2. Create a Content Type Definition corresponding to our project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Label: Project&lt;/li&gt;
&lt;li&gt;  API Name: project&lt;/li&gt;
&lt;li&gt;  Properties:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffn2n39qanunywjq2w541.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffn2n39qanunywjq2w541.png" alt="1-add-content-type.png" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3. Use the Slug plugin, to simplify content entry. This will automatically generate a slug from a specified field.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Go to Plugins.&lt;/li&gt;
&lt;li&gt;  Click “+” next to the Slug plugin.&lt;/li&gt;
&lt;li&gt;  Complete the configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5iipu7vrk68xegjr5752.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5iipu7vrk68xegjr5752.png" alt="2-plugins.png" width="800" height="420"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6pmrgxv1k44mtnmqn3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6pmrgxv1k44mtnmqn3i.png" alt="3-slug-settings.png" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4. Enter sample data into Flotiq. A few sample entries will suffice:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9v4s35a20f93psjhdely.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9v4s35a20f93psjhdely.png" alt="4-objects-of-type-project.png" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Next.js application setup
&lt;/h2&gt;

&lt;p&gt;Navigate to the desired directory and run the following command to create a new Next.js application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q0vacseq13i1dgk81wc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q0vacseq13i1dgk81wc.png" alt="image.png" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the application directory and run the application&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, you will see the default Next.js homepage.&lt;/p&gt;

&lt;p&gt;Now, it's time to add content from Flotiq.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Connecting Flotiq with Next.js &lt;a href="https://flotiq.com/get-started-with-flotiq-sdk/" rel="noopener noreferrer"&gt;using a personalized SDK&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The API Key is available in the Flotiq panel, under the "API Keys" tab. Copy the "Read and write API KEY".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvsfkj2pqzeq6alf288g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvsfkj2pqzeq6alf288g.png" alt="image (53).png" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setting up environment variables
&lt;/h3&gt;

&lt;p&gt;Create a &lt;em&gt;.env.local&lt;/em&gt; file and paste the value copied from Flotiq into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FLOTIQ_API_KEY=___YOUR_READ_ONLY_API_KEY___
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting up Flotiq SDK
&lt;/h3&gt;

&lt;p&gt;In projects built with TypeScript, we recommend using the &lt;a href="https://flotiq.com/get-started-with-flotiq-sdk/" rel="noopener noreferrer"&gt;personalized Flotiq SDK&lt;/a&gt;. It wraps the REST API, providing convenient access to data tailored to your data model, including data types and intelligent code completion.&lt;/p&gt;

&lt;p&gt;Download the Flotiq SDK for your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx flotiq-codegen-ts@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executing the command will download the SDK based on your Content Type Definitions. Note that the script used the previously pasted key from the &lt;em&gt;.env.local&lt;/em&gt; file. A directory named &lt;em&gt;flotiqApi&lt;/em&gt; has been created in your application directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbl27uswqoy4z6rk0g2tr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbl27uswqoy4z6rk0g2tr.png" alt="kod3.png" width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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: &lt;em&gt;npx flotiq-codegen-ts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Fetch content from Flotiq using Flotiq SDK
&lt;/h3&gt;

&lt;p&gt;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 &lt;em&gt;/src/lib/api.ts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Create a file &lt;em&gt;/src/lib/api.ts&lt;/em&gt; where we will place the basic requests needed to complete the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { FlotiqApi, Project, ProjectList } from "../../flotiqApi/src";

type ProjectFilter = {
  slug?: {
    type: string,
    filter: string
  }
}

export async function fetchAllProjects(): Promise&amp;lt;ProjectList&amp;gt; {
  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&amp;lt;Project | undefined&amp;gt; {
  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];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 10: The &lt;em&gt;fetchAllProjects&lt;/em&gt; method retrieves all objects of the Project type.&lt;/li&gt;
&lt;li&gt;  Line 20: The &lt;em&gt;fetchProjectBySlug&lt;/em&gt; method retrieves a Project object that has a specific value in the &lt;em&gt;slug&lt;/em&gt; field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Now we are ready to display content from Flotiq.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Displaying the list of articles and a single article
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing projects on the application's homepage
&lt;/h3&gt;

&lt;p&gt;1. Prepare app configuration to display images from Flotiq host. Thanks to that, we can utilize the &lt;a href="https://nextjs.org/docs/pages/api-reference/components/image" rel="noopener noreferrer"&gt;next/image&lt;/a&gt; component.&lt;/p&gt;

&lt;p&gt;Update the file &lt;em&gt;nextjs.config.ts&lt;/em&gt; to containg the following configuration in the &lt;em&gt;images&lt;/em&gt; key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/** @type {import('next').NextConfig} */
const nextConfig = {
    images: {
        remotePatterns: [
            {
                protocol: 'https',
                hostname: 'api.flotiq.com',
                port: '',
                pathname: '/**',
            },
        ],
    },
};

export default nextConfig;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: the change may require rerunning the command &lt;em&gt;yarn dev&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;2. Replace the example homepage code defined in _/app/page.ts_x.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 (
    &amp;lt;main className="max-w-6xl mx-auto px-4 py-10"&amp;gt;
      &amp;lt;h1 className="text-2xl font-bold mb-8 border-b-2 border-gray-600"&amp;gt;Our Projects&amp;lt;/h1&amp;gt;
      &amp;lt;div className="grid grid-cols-1 md:grid-cols-3 gap-6"&amp;gt;
        {
          projects.data?.map((project: Project) =&amp;gt; (
            &amp;lt;article key={project.id}&amp;gt;
              &amp;lt;Link href={`/projects/${project.slug}`}&amp;gt;
                &amp;lt;h2 className="text-xl font-semibold mb-2"&amp;gt;{project.name}&amp;lt;/h2&amp;gt;
                &amp;lt;Image
                  src={`https://api.flotiq.com${project.photos?.[0].url}`}
                  alt={project.name}
                  className="w-full h-auto rounded"
                  width={400}
                  height={300}
                /&amp;gt;
              &amp;lt;/Link&amp;gt;
            &amp;lt;/article&amp;gt;
          ))
        }
      &amp;lt;/div&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 7: &lt;em&gt;const projects = await fetchAllProjects();&lt;/em&gt; - fetching Projects to display, using the previously prepared API.&lt;/li&gt;
&lt;li&gt;  Line 10: &lt;em&gt;className="max-w-6xl mx-auto px-4 py-10"&lt;/em&gt; - using TailwindCSS classes for basic component styling.&lt;/li&gt;
&lt;li&gt;  Line 14: &lt;em&gt;projects.data.map((project: Project) =&amp;gt; (&lt;/em&gt; - listing Projects, note that we can use the &lt;em&gt;Project&lt;/em&gt; type, which provides attribute suggestions for this object.&lt;/li&gt;
&lt;li&gt;  Line 20: &lt;em&gt;{project.name}&lt;/em&gt; - displaying an object's attribute, e.g., the name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check how your application looks. Go to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; and see the results. The homepage should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma0vh5g28ur6qtbzx6tx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma0vh5g28ur6qtbzx6tx.png" alt="homepage1.png" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Displaying a single Project
&lt;/h3&gt;

&lt;p&gt;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 &lt;em&gt;/projects/[slug]&lt;/em&gt; address.&lt;/p&gt;

&lt;p&gt;1. Add the Tailwind &lt;a class="mentioned-user" href="https://dev.to/tailwindcss"&gt;@tailwindcss&lt;/a&gt;/typography plugin&lt;/p&gt;

&lt;p&gt;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., &lt;em&gt;h1&lt;/em&gt;, &lt;em&gt;h2&lt;/em&gt;, &lt;em&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/em&gt;), we need to extend Tailwind with the &lt;a class="mentioned-user" href="https://dev.to/tailwindcss"&gt;@tailwindcss&lt;/a&gt;/typography plugin. Otherwise, they will not be styled, as Tailwind relies solely on classes, not HTML tags.&lt;/p&gt;

&lt;p&gt;Add the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @tailwindcss/typography
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the plugin in the &lt;em&gt;tailwind.config.ts&lt;/em&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ...other tailwind config

plugins: [
  require('@tailwindcss/typography'),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Create a single project page &lt;em&gt;/app/projects/[slug]/page.tsx&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes" rel="noopener noreferrer"&gt;Dynamic Route&lt;/a&gt; convention in NextJS, the &lt;em&gt;page.tsx&lt;/em&gt; file placed in the &lt;em&gt;[slug]&lt;/em&gt; directory will be responsible for displaying the content of pages where &lt;em&gt;[slug]&lt;/em&gt; will be replaced by possible values from the browser address.&lt;/p&gt;

&lt;p&gt;Here is the source code for a single project page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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) =&amp;gt; ({
    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 (
    &amp;lt;main className="max-w-6xl mx-auto px-4 py-10"&amp;gt;
      &amp;lt;h1 className="text-2xl font-bold mb-8 border-b-2 border-gray-600"&amp;gt;{project.name}&amp;lt;/h1&amp;gt;
      &amp;lt;div className="grid grid-cols-2 gap-6"&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;Image
            src={`https://api.flotiq.com${project.photos?.[0].url}`}
            alt={project.name}
            className="w-full h-auto rounded"
            width={400}
            height={300}
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;div
            dangerouslySetInnerHTML={{__html: project.description ?? ''}}
            className="prose prose-invert"
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;Link href="/"&amp;gt;Back&amp;lt;/Link&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 7: The &lt;em&gt;generateStaticParams&lt;/em&gt; function is responsible for generating all possible paths based on the &lt;em&gt;slug&lt;/em&gt; field.&lt;/li&gt;
&lt;li&gt;  Line 21: Using the previously prepared &lt;em&gt;fetchProjectBySlug&lt;/em&gt; function to fetch projects from Flotiq.&lt;/li&gt;
&lt;li&gt;  Line 43: Using the classes “prose prose-invert”, i.e., &lt;em&gt;className="prose prose-invert"&lt;/em&gt;, so that the code returned by Flotiq in the description field (Rich Text type) will be correctly styled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You just need to navigate to the specific project page, in our case &lt;a href="http://localhost:3000/projects/smart-home-automation-system" rel="noopener noreferrer"&gt;http://localhost:3000/projects/smart-home-automation-system&lt;/a&gt; to display the finished page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhiqliiwdnaunv0bv88x6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhiqliiwdnaunv0bv88x6.png" alt="homepage2.png" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Building a statically generated application
&lt;/h2&gt;

&lt;p&gt;Currently, we have a page with a list of projects and a detailed project page, both optimized for using React Server Components and &lt;a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components#static-rendering-default" rel="noopener noreferrer"&gt;static rendering&lt;/a&gt;. This provides exceptional application speed; however, its content is updated only during the build process.&lt;/p&gt;

&lt;p&gt;Create a production-optimized application by running the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, run the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt; 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 &lt;em&gt;yarn build&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To make our page respond to changes in Flotiq, we will prepare an &lt;a href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation" rel="noopener noreferrer"&gt;On-demand Revalidation&lt;/a&gt; mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Implementing the on-demand revalidation mechanism
&lt;/h2&gt;

&lt;p&gt;To make our page behave dynamically, responding to events from Flotiq, we will prepare an &lt;a href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation" rel="noopener noreferrer"&gt;On-demand Revalidation&lt;/a&gt; strategy in two steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Create an API endpoint&lt;/strong&gt; that, when queried, will clear the cache for specific addresses.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Create Webhooks in Flotiq&lt;/strong&gt; that, upon content edit actions, will trigger the cache revalidation API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating the revalidate API in Next.js
&lt;/h3&gt;

&lt;p&gt;Our application will include an additional endpoint, for example, &lt;em&gt;/api/revalidate&lt;/em&gt;, which, when queried, will clear the cache within the specified range - for given paths. We will prepare a &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" rel="noopener noreferrer"&gt;route handler&lt;/a&gt; for this purpose, using the &lt;a href="https://nextjs.org/docs/app/api-reference/functions/revalidatePath" rel="noopener noreferrer"&gt;revalidatePath&lt;/a&gt; method.&lt;/p&gt;

&lt;p&gt;Add a key to the &lt;em&gt;.env.local&lt;/em&gt; file that we will use to secure the cache endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REVALIDATE_KEY=secret_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create file &lt;em&gt;/src/app/api/revalidate/route.ts&lt;/em&gt;, in which we will handle /api/revalidate&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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() });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 8: Checking whether the correct &lt;em&gt;x-revalidate-key&lt;/em&gt; endpoint was entered - it shouldn’t be accessible without security.&lt;/li&gt;
&lt;li&gt;  Line 12: Using the function &lt;em&gt;revalidatePath(path, 'page')&lt;/em&gt; informing Next.js that the path should be re-fetched the next time it is loaded. The second parameter of the function is used to tell Next.js whether to refresh a specific path, a dynamic path, or a layout. You can read more about using this function in the &lt;a href="https://nextjs.org/docs/app/api-reference/functions/revalidatePath" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see in practice how the revalidate function works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Build the application using the &lt;em&gt;yarn build&lt;/em&gt; command&lt;/li&gt;
&lt;li&gt; Start the server using the &lt;em&gt;yarn start&lt;/em&gt; command&lt;/li&gt;
&lt;li&gt; Go to &lt;a href="https://localhost:3000" rel="noopener noreferrer"&gt;https://localhost:3000&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; Change the data of a project in Flotiq, eg. change the title.&lt;/li&gt;
&lt;li&gt; Refresh the site &lt;a href="https://localhost:3000" rel="noopener noreferrer"&gt;https://localhost:3000&lt;/a&gt; - no changes visible.&lt;/li&gt;
&lt;li&gt; Execute the revalidate request for the path "/" (for the homepage): &lt;em&gt;curl --location --request POST '&lt;a href="http://localhost:3000/api/revalidate?path=/" rel="noopener noreferrer"&gt;http://localhost:3000/api/revalidate?path=/&lt;/a&gt;' \ --header 'x-revalidate-key: secret_key'&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; Refresh the site https//localhost:3000 - changes should be visible now.&lt;/li&gt;
&lt;li&gt; Execute the revalidate request, for the path “/projects/[slug]” &lt;em&gt;curl --location --request POST '&lt;a href="http://localhost:3000/api/revalidate?path=/projects/%5C%5Bslug%5C%5D" rel="noopener noreferrer"&gt;http://localhost:3000/api/revalidate?path=/projects/\[slug\]&lt;/a&gt;' \ --header 'x-revalidate-key: secret_key'&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; Changes should also be visible on the project subpages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;In the next section, we will automate the process using Flotiq Webhooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating webhooks in Flotiq
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;br&gt;&lt;br&gt;
To use webhooks with a local application (running on localhost), you can also use ngrok to make your local application accessible from the outside.&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We need to add two webhooks that will execute after the change in Content Type Definition Project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;http://[APP_PUBLIC_URL]/api/revalidate?path=/&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;http://[APP_PUBLIC_URL]/api/revalidate?path=/projects/[slug]&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the first webhook 'Revalidate homepage cache', providing the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Type: Content Object Changes Asynchronous&lt;/li&gt;
&lt;li&gt;  URL: &lt;em&gt;&lt;a href="http://http://%5C%5BAPP%5C_PUBLIC%5C_URL%5C%5D/api/revalidate?path=/" rel="noopener noreferrer"&gt;http://http://\[APP\_PUBLIC\_URL\]/api/revalidate?path=/&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  Enabled: true&lt;/li&gt;
&lt;li&gt;  Actions: Create, Update, Delete&lt;/li&gt;
&lt;li&gt;  Content Type Definitions: Projects&lt;/li&gt;
&lt;li&gt;  Headers: x-revalidate-key, secret_key (remember that the value of &lt;em&gt;secret_key&lt;/em&gt; should match the REVALIDATE_KEY value that we’ve defined in the &lt;em&gt;.env.local&lt;/em&gt; file)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzimp8wkdc2e7h1duift.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzimp8wkdc2e7h1duift.png" alt="edit-webhook.png" width="800" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create another webhook ‘Revalidate project pages cache’, providing the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Type: Content Object Changes Asynchronous&lt;/li&gt;
&lt;li&gt;  URL: &lt;em&gt;http://[APP_PUBLIC_URL]/api/revalidate?path=/projects/[slug]&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  Enabled: true&lt;/li&gt;
&lt;li&gt;  Actions: Create, Update, Delete&lt;/li&gt;
&lt;li&gt;  Content Type Definitions: Projects&lt;/li&gt;
&lt;li&gt;  Headers: x-revalidate-key, secret_key (remember that the value of &lt;em&gt;secret_key&lt;/em&gt; should match the REVALIDATE_KEY value that we’ve defined in the &lt;em&gt;.env.local&lt;/em&gt; file)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F780ccuqgrdpqb5ye7dmx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F780ccuqgrdpqb5ye7dmx.png" alt="edit-webhook2.png" width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Summing up, our webhook list should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yy55cir4vmawgdj49in.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yy55cir4vmawgdj49in.png" alt="webhooks.png" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make sure that a given webhook works, you can check its execution log available in the webhook’s edit view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohx4mp7t4d6cen524843.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohx4mp7t4d6cen524843.png" alt="webhook-execution-log.png" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember, for the webhook to execute, your application must be accessible from the outside. This requires either publishing the application or using services like &lt;a href="https://ngrok.com/docs/getting-started/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;In the next stage, we will use another important feature of Next.js - draft mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Implementing Next.js &lt;a href="https://nextjs.org/docs/app/building-your-application/configuring/draft-mode" rel="noopener noreferrer"&gt;Draft Mode&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Our intention is to allow Content Editors to work on content without publishing it. Hence the need to mark some content as "Draft".&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating the project for Draft Mode
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;1. Add an environment variable to the &lt;em&gt;.env.local&lt;/em&gt; file, which will be used to secure the preview mode endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DRAFT_MODE_KEY=secret_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Add Draft Mode information to the homepage template. Thanks to this, the application will know in which mode it is operating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we pass the &lt;em&gt;isEnabled&lt;/em&gt; information to our function &lt;em&gt;fetchAllProjects&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;3. Similarly, add Draft Mode information to the project page template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Let's extend the methods in our &lt;em&gt;src/lib/api.ts&lt;/em&gt; file responsible for communicating with Flotiq. Replace the file with the one containing the handling of the &lt;em&gt;draftMode&lt;/em&gt; parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 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&amp;lt;ProjectList&amp;gt; {
  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&amp;lt;Project | undefined&amp;gt; {
  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];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 22: Extending the &lt;em&gt;ProjectFilter&lt;/em&gt; type with a new attribute: &lt;em&gt;status&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;  Lines 22 and 45: Conditionally adding the &lt;em&gt;filters.status&lt;/em&gt; filter when we are outside of Draft Mode. This means that when we are not using Draft Mode, we only display content with the status “public”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that on the project’s homepage, at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, only projects marked with the "public" flag in Flotiq are visible.&lt;/p&gt;

&lt;p&gt;Now, we will add an API endpoint to instruct the application to also show content with draft status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Draft Mode API in Next.js
&lt;/h3&gt;

&lt;p&gt;Add a handler to process the path &lt;em&gt;/api/draft?slug=/&amp;amp;key=secret_key.&lt;/em&gt; In accordance with the Next.js convention, place it in the &lt;em&gt;/app/api/draft/route.ts&lt;/em&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 11: We handle enabling and disabling draft mode. When enabling Draft Mode, for security reasons, we require the use of the &lt;em&gt;DRAFT_MODE_KEY&lt;/em&gt; token.&lt;/li&gt;
&lt;li&gt;  Line 15: Enabling draft mode using the built-in Next.js function.&lt;/li&gt;
&lt;li&gt;  Linia 20: Disabling draft mode using the built-in Next.js function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Draft Mode handling is ready! By navigating to &lt;a href="http://localhost:3000/api/draft?secret=secret_key" rel="noopener noreferrer"&gt;http://localhost:3000/api/draft?secret=secret_key&lt;/a&gt;, you should see all posts on the homepage, including those with the &lt;em&gt;draft&lt;/em&gt; status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxfskhejrkf0nfdtbwh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxfskhejrkf0nfdtbwh5.png" alt="homepage3.png" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next step, we will propose a minor improvement to make using Draft Mode easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a component to indicate if the page is in Draft Mode
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qhncrj6ew1ykbt9o62d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qhncrj6ew1ykbt9o62d.png" alt="homepage4.png" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add the &lt;em&gt;DraftModeToolbar&lt;/em&gt; component, add the &lt;em&gt;src/app/ui/DraftModeToolbar.tsx&lt;/em&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use client'

import { usePathname } from "next/navigation";

export default function DraftModeToolbar() {
  const pathName = usePathname();

  return (
    &amp;lt;div className="fixed top-0 right-0 py-2 px-4 bg-red-500"&amp;gt;
      &amp;lt;p&amp;gt;Draft mode enabled&amp;lt;/p&amp;gt;
      &amp;lt;a href={`/api/draft/?disable=true&amp;amp;slug=${pathName}`}&amp;gt;Disable&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 1: Add the "use client" directive, informing Next.js that the following code will execute on the client side. We need this to use the &lt;em&gt;usePathname()&lt;/em&gt; hook, which returns the current URL.&lt;/li&gt;
&lt;li&gt;  Line 11:Link to exit Draft Mode, referring to the previously defined API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use this component in the project layout, in the &lt;em&gt;src/app/layout.tsx&lt;/em&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/app/layout.tsx
// ...
import { draftMode } from "next/headers";
import DraftModeToolbar from "@/app/ui/DraftModeToolbar";
// ...

export default function RootLayout({children}: Readonly&amp;lt;{children: React.ReactNode;}&amp;gt;) {
  const { isEnabled } = draftMode();
  return (
    &amp;lt;html lang="en"&amp;gt;
      &amp;lt;body className={inter.className}&amp;gt;
        { isEnabled &amp;amp;&amp;amp; &amp;lt;DraftModeToolbar/&amp;gt; }
        { children }
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Line 8: Fetching information on whether Draft Mode in enabled.&lt;/li&gt;
&lt;li&gt;  Line 12: When Draft Mode is enabled, display DraftModeToolbar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The final step is to integrate the Flotiq interface with Draft Mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a preview link in Flotiq
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlp20b49gkh8bclo9gxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlp20b49gkh8bclo9gxx.png" alt="preview.png" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1. In your Flotiq account, add the “Custom Links” plugin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe34d1a9akf05pe8e2me4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe34d1a9akf05pe8e2me4.png" alt="custom-links-plugin.png" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Preview page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  URL Template: &lt;a href="http://localhost:3000/api/draft?slug=/projects/%7Bslug%7D&amp;amp;secret=secret%5C_key" rel="noopener noreferrer"&gt;http://localhost:3000/api/draft?slug=/projects/{slug}&amp;amp;secret=secret\_key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Link name template: Preview page&lt;/li&gt;
&lt;li&gt;  Content types: Project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open published page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  URL Template &lt;a href="http://localhost:3000/api/draft?slug=/projects/%7Bslug%7D&amp;amp;secret=secret%5C_key&amp;amp;disable=true" rel="noopener noreferrer"&gt;http://localhost:3000/api/draft?slug=/projects/{slug}&amp;amp;secret=secret\_key&amp;amp;disable=true&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Link name template: Open published page&lt;/li&gt;
&lt;li&gt;  Content types: Project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s what the configuration should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34hbl8syswafcni0rcbl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34hbl8syswafcni0rcbl.png" alt="custom-links-settings.png" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;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.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Go live! Deployment to Vercel
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The deployment process is intuitive: connect your repository from GitHub, GitLab, or Bitbucket, and Vercel will automatically build the application after each push.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing repository to GitHub
&lt;/h3&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;p&gt;Create a GitHub repository and upload the project files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Importing git repository to a Vercel project
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ej5qzvpukba14h8jt3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ej5qzvpukba14h8jt3h.png" alt="vercel1.png" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After entering the configuration view, don’t forget to enter the environment variables corresponding to those in your &lt;em&gt;.env.local&lt;/em&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnjmnrbqsrnnietzj8a5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnjmnrbqsrnnietzj8a5.png" alt="vercel2.png" width="800" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy
&lt;/h4&gt;

&lt;p&gt;Click the “Deploy” button. The operation may take a few minutes.&lt;/p&gt;

&lt;p&gt;Once the build process is complete, the site will be available, and you can find its address on the Vercel dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjdx8jk3b5piykzqi35d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjdx8jk3b5piykzqi35d.png" alt="vercel3.png" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From now on, the site is available to the public.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update webhooks and custom links in Flotiq
&lt;/h3&gt;

&lt;p&gt;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 &lt;em&gt;APP_PUBLIC_URL&lt;/em&gt; with the actual values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltzts17cj70l0k60ih2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltzts17cj70l0k60ih2o.png" alt="webhooks2.png" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For plugins, replace the previous &lt;em&gt;&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/em&gt; with the domain set on Vercel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzflkw2yh0eixpsymjsp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzflkw2yh0eixpsymjsp.png" alt="custom-link-settings2.png" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The components used—SDKs, webhooks, and plugins—demonstrate how to conveniently utilize Flotiq and adapt it to various types of projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/trzcina/flotiq-nextjs-projects-app" rel="noopener noreferrer"&gt;The code for the application prepared for the article is available in a public GitHub repository.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>headlesscms</category>
      <category>webdev</category>
      <category>flotiq</category>
    </item>
    <item>
      <title>From Gatsby to Next.js: Why We Migrated Our Blog and How You Can Too</title>
      <dc:creator>Paweł Panowicz</dc:creator>
      <pubDate>Thu, 09 Jan 2025 10:05:53 +0000</pubDate>
      <link>https://forem.com/flotiq/from-gatsby-to-nextjs-why-we-migrated-our-blog-and-how-you-can-too-e96</link>
      <guid>https://forem.com/flotiq/from-gatsby-to-nextjs-why-we-migrated-our-blog-and-how-you-can-too-e96</guid>
      <description>&lt;h2&gt;
  
  
  Why We Migrated Our Blog From Gatsby to Next.js and How You Can Too
&lt;/h2&gt;

&lt;p&gt;When we first launched the &lt;strong&gt;Flotiq Blog&lt;/strong&gt;, Gatsby seemed like the perfect choice. Its static site generation and focus on performance aligned with our vision for a fast, reliable platform. But as Gatsby’s limitations became more apparent—particularly its declining relevance as a technology and the challenges we faced with content updates—we realized it was time to reevaluate our approach. This is the story of how we transitioned from Gatsby to Next.js, the issues that prompted the move, and why this migration transformed our blog for the better.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Road to Migration: Why migrate from Gatsby to Next.js
&lt;/h2&gt;

&lt;p&gt;At Flotiq, we’re always looking for ways to improve - whether it’s enhancing our product, refining processes, or adopting better technology. Initially, Gatsby worked well for our blog. It was fast, simple, and had a vibrant ecosystem of plugins. But over time, cracks started to show.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decline of Gatsby
&lt;/h2&gt;

&lt;p&gt;Gatsby, once a favorite among developers for building static sites, has been losing traction in 2024. On below visual from StateOfJs, you can observe change of sentiment among dev community for Gatsby and Next.js. While Gatsby sentiment goes down year to year, Next.js grows, not only with positive sentiment but also with amount of people using it. Also community shifts towards Next.js, which is visible in growing resources and toolkits around Next.js.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rdktm9euchqdcwse2qt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rdktm9euchqdcwse2qt.png" width="800" height="472"&gt;&lt;/a&gt;&lt;em&gt;Source: &lt;a href="https://2024.stateofjs.com/en-US/libraries/" rel="noopener noreferrer"&gt;https://2024.stateofjs.com/en-US/libraries/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s more detailed breakdown:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Evolving Priorities&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;After Netlify’s acquisition of Gatsby, the framework’s development trajectory shifted. With a focus on integrating with Netlify’s ecosystem, Gatsby’s innovation appears to be gone. Throughout the entire 2024 we’ve seen &lt;a href="https://github.com/gatsbyjs/gatsby/releases" rel="noopener noreferrer"&gt;only 1 minor update in the 5.x branch&lt;/a&gt; and even that consists mainly of bumped dependencies and small fixes:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztrgk4vwk2axdve7bw5i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztrgk4vwk2axdve7bw5i.png" width="800" height="513"&gt;&lt;/a&gt;This raises serious concerns about Gatsby’s long-term viability and relevance for projects like ours. Many developers have already moved on to frameworks like Next.js, which offer greater flexibility and performance for modern web applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Dynamic Content Limitations and unreliable Preview mode&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;As we expanded, we needed a way to handle dynamic routes and real-time content updates. Gatsby’s reliance on static generation made this cumbersome. Each post change/publication required build on production, it added 5 min to see each change.&lt;/p&gt;

&lt;p&gt;Gatsby’s Preview mode required deploying a separate Gatsby site in development mode, which often proved unstable and cumbersome to manage. This lack of a reliable and straightforward preview solution made content updates and collaboration with our team more challenging, ultimately pushing us to seek a more robust alternative. The time needed to regenerate static pages after each content update was just too long for an effective editorial workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Plugin Overload&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Gatsby’s plugin ecosystem is both a strength and a weakness. Managing and updating Gatsby plugins became a noticable overhead, in several cases plugin versions were not compatible with each other, similar to the situation hapenning in the Wordpress plugin ecosystem. Of course - overall the plugin system offered by Gatsby is far more stable than Wordpress.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Next.js Stood Out
&lt;/h2&gt;

&lt;p&gt;Next.js emerged as the clear winner for our needs. It’s backed by Vercel, has a robust community, and offers the hybrid rendering approach we needed - SSG, SSR (server-side rendering), and even ISR (incremental static regeneration), all in one package. With Next.js, we saw the opportunity to create a blog that was more flexible, easier to maintain, and better aligned with the evolving needs of modern web development.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration: Step-by-Step Guide to Migrate from Gatsby to Next.js
&lt;/h2&gt;

&lt;p&gt;At first - our dev team approached this with reservations, as every migration - the project had some risks and there was some internal pressure from our Growth team to deliver the new Blog before Christmas. However, after breaking down the migration and realizing that most of the content remains untouched - developers grew more confident that this can actually work. After all - having the ability to easily change frontends is one of the selling points of headless CMS!&lt;/p&gt;

&lt;p&gt;Here’s how we tackled this project:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Assessing the Existing Site&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The first step was understanding what we had. We analyzed the structure of our Gatsby blog, noting critical components like:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Routes&lt;/strong&gt;: Static and dynamic pages.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Content&lt;/strong&gt;: Pulled from Flotiq, our headless CMS.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SEO Elements&lt;/strong&gt;: Titles, meta descriptions, and Open Graph tags.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Setting Up a New Next.js Project&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We created a fresh Next.js project and configured it to work seamlessly with Flotiq. Thanks to Next.js’s flexibility, this was straightforward. We used npx create-next-app to get started and integrated Flotiq using its API.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Migrating Routes and Page Creation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Routes in Gatsby can be created both programmatically, as well as using static files. Because of that - it’s not necessarily obvious how to identify all existing routes in your project. To have 100% certainty that we didn’t loose any page we ran Gatsby’s local development server and connected to the GraphQL API interface to fetch the results of this query:  &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  allSitePage {
    nodes {
      path
      component
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;  This returns a list of all pages in a Gatsby project, along with the path of JS file that was used as the template to generate each page, like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "data": {
    "allSitePage": {
      "nodes": [
        {
          "path": "/",
          "component": "/Users/MyUser/Desktop/flotiq/flotiq-blog/src/templates/index.js"
        },
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We took this output and ran it through a simple piece of Javascript, to obtain a list of pages organized by template name:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const getRoutes = (pages) =&amp;gt; {
  const uniqueRoutes = [];
  const basePath = 'flotiq-blog';
  pages.forEach((page) =&amp;gt; {
    
    const relPath = page.component.substring(page.component.indexOf(basePath) + basePath.length+1);
    const arrayKey = relPath === "//" ? "/" : relPath;
    if(typeof (uniqueRoutes[arrayKey]) === 'undefined' ){
        uniqueRoutes[arrayKey] = [];
    }
    uniqueRoutes[arrayKey].push(page.path);
  });
  return uniqueRoutes;
};

console.log(getRoutes(allSitePage.data.allSitePage.nodes));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;which returns the following hierarchy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrmodhfwq9yyxnxft3wh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrmodhfwq9yyxnxft3wh.png" width="800" height="71"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That helped us tremendously when creating routes and template files. For each template - we had to analyze the React components used and adapt them to match Next.js conventions and requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Transferring SEO Metadata&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Preserving our SEO was a top priority. We ensured all meta tags, titles, and descriptions were correctly migrated to Next.js using generateMetadata function. By maintaining the same URLs, we avoided breaking backlinks or losing traffic.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Data Fetching with Flotiq&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Gatsby’s approach to data fetching is a 2-step process:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Data has to be fetched from the source (in our case - Flotiq, using our source plugin gatsby-source-flotiq) into Gatsby’s internal GraphQL server&lt;/li&gt;
&lt;li&gt;  GraphQL queries are made from templates to fetch the data so it can be displayed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s nice for people who prefer GraphQL, but also a bit complicated at times.&lt;br&gt;&lt;br&gt;
In our Next.js setup we decided to simplify this, so our developers have more control over the data fetching process, and also - to easily get a fully typed API.&lt;br&gt;&lt;br&gt;
Using Flotiq codegen SDK for Typescript we were able to develop quickly, with intuitive code completion and syntax highlighting in our IDE. This is a major step forward in terms of developer experience.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xgaqci4ctui83qqzx64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xgaqci4ctui83qqzx64.png" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. Testing and Optimization&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Before deploying, we rigorously tested the new blog. We checked:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Page load times and performance using Lighthouse and &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;https://pagespeed.web.dev/&lt;/a&gt; .&lt;/li&gt;
&lt;li&gt;  SEO elements using tools like Google Search Console.&lt;/li&gt;
&lt;li&gt;  Compatibility across devices and browsers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Deployment&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, we deployed the new blog to &lt;strong&gt;Vercel&lt;/strong&gt;, taking advantage of its automatic builds, previews, and fast global CDN.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Benefits of Migrating Gatsby to Next.js
&lt;/h2&gt;

&lt;p&gt;Switching from Gatsby to Next.js brought immediate and long-term advantages:  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Faster Build Times and scalability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With Server-side Rendering (SSR)and selective cache revalidating, we no longer rebuild the entire site for each update. This made updating of the content frictionless. As our content grows, we can confidently scale without worrying about build bottlenecks or performance drops.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Dynamic Content Made Easy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next.js’s hybrid rendering lets us combine static pages with server-side rendering for dynamic content. This made it easier to keep our blog fresh and responsive.  &lt;/p&gt;

&lt;p&gt;With Gatsby, publishing or updating a single blog post was a tedious process. Each change required a full deployment to production, which could take around 5 minutes or more depending on the content. This meant delays and interruptions, especially for quick updates or time-sensitive posts.&lt;br&gt;&lt;br&gt;
With Next.js, the process is seamless - updates are triggered by a simple webhook that refreshes the cache almost instantly. This efficiency has not only saved us time but also made our workflow far more dynamic and responsive.&lt;br&gt;&lt;br&gt;
Here’s how we configured it in Flotiq&lt;br&gt;&lt;br&gt;
 &lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauz7o7w3k0o6h8m9xvas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauz7o7w3k0o6h8m9xvas.png" width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Improved SEO&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In our previous setup we used Gatsby with Cloudflare Pages, to squeeze the best performance possible for our readers. We still recommend Cloudflare Pages, but we’ve seen many error reports, when the pages were not rendered properly (and based on these Github discussions - &lt;a href="https://github.com/gatsbyjs/gatsby/issues/16097" rel="noopener noreferrer"&gt;we’re not alone&lt;/a&gt; ) in this setup. Specifically, it seemed to impact the indexing of our blog by Google, especially combined with the fact that we were hosting the blog in a sub-path (&lt;a href="https://flotiq.com/blog/?utm_campaign=Gatsby_Nextjs_post_blog_link&amp;amp;utm_medium=referral&amp;amp;utm_source=dev-to" rel="noopener noreferrer"&gt;flotiq.com/blog/&lt;/a&gt;). At some point we even migrated the blog to a separate domain (blog.flotiq.com) to help solve these issues, without much improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Simplified Maintenance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Without the plugin dependencies of Gatsby, updates and troubleshooting are now simpler and quicker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Considerations to keep in mind when migrating from Gatsby to Next.js  
&lt;/h2&gt;

&lt;p&gt;If you’re planning a similar migration, keep these points in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preserve Your URLs&lt;/strong&gt;: Maintaining your existing URL structure is essential for SEO and avoiding broken links.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus on Metadata&lt;/strong&gt;: Ensure all titles, descriptions, and Open Graph tags are correctly migrated.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test, Test, Test&lt;/strong&gt;: Validate every page and feature to avoid surprises post-deployment.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leverage Your CMS&lt;/strong&gt;: Having a headless CMS backend makes it easy to replace the frontend technology without changes to the data.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next.js and Flotiq: A Perfect Match
&lt;/h2&gt;

&lt;p&gt;The combination of &lt;strong&gt;Next.js&lt;/strong&gt; and &lt;strong&gt;Flotiq&lt;/strong&gt; proved to be a winning formula. Next.js’s modern features, combined with Flotiq’s flexible headless CMS capabilities, allowed us to create a blog that’s fast, scalable, and easy to manage. Whether you’re starting a new project or considering a migration, this duo is ideal for building robust, future-proof websites.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to make the switch?&lt;/strong&gt; Explore &lt;a href="https://editor.flotiq.com/register?utm_campaign=Gatsby_Nextjs_blog_post_bottom_CTA&amp;amp;utm_medium=referral&amp;amp;utm_source=dev-to" rel="noopener noreferrer"&gt;Flotiq&lt;/a&gt; and unlock the full potential of Next.js for your next project!  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any questions?&lt;/strong&gt; Write to me at &lt;a href="//mailto:pawel@flotiq.com"&gt;pawel@flotiq.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>headlesscms</category>
    </item>
  </channel>
</rss>
