Building a blog with Squidex and Next.js
How to build a Next.js blog powered by content from Squidex CMS.
This tutorial will guide you to create a blog application, set up its schema and add mock content on Squidex. Then demonstrate how to create a Next.js blog based on the data stored in your CMS.
To complete this guide, you need:
In Squidex, an application is an isolated data store with schemas that define how its content is structured and API endpoints to query its content. You can visit this page of the Squidex docs to learn more about apps in Squidex.
Start by logging into your Squidex account dashboard at https://cloud.squidex.io/. Click New App (1) to create an empty application. Enter a unique name (2) for the app in the space provided and click Create (3) to create the app. For the purposes of this guide, we will assume the name of your Squidex application is
squidex-blog
.App names in cloud.squidex.io are global, hence the name has to be unique. You can add your name initials or a word to make it unique.

Create an empty app
Now that the app is created on Squidex, let's proceed to create the schema for the blog application. This schema will define the structure of your content.
On the Squidex dashboard, click squidex-blog (or whatever name you chose) to enter the dashboard for the newly created app.

Select the new app
On the left panel, select Schemas (5) and click the Plus(+) (6) button to start creating the posts schema. Enter
posts
in the space provided under Name (7), select Multiple contents (8) since you will be having many posts, and click Create (9) to create the schema.
Create the 'posts' schema
The posts schema will have 3 fields i.e.
Title
for the title of your posts, Slug
to define the URL of your posts, and Content
to contain the body of your blog posts.Field name | Type | Editor |
---|---|---|
Title | String | Input |
Slug | String | Slug |
Content | String | Markdown |
Ensure you are in posts (11) schema under Schemas (10) and click +Add Field (12) to start adding fields.

Adding fields
Create the
Title
field in the modal window, select String (13) enter Title
(14) in the field provided and click Create and edit field (15) as shown below.
Create the Title field
Click the Validation (16) tab on the new modal window that opens up, check Required (17). This is needed as every post must have a title, hence we are making it a required / mandatory field. Click Save and add field (18) to save your changes.

Configure validation properties of title field
Repeat the steps above to create the rest two field i.e,
Slug
and Content
. The
Slug
field is configured as shown in the screenshots below:
Create slug field
Since the every post must have a slug and it has to be unique, complete the Validation tab in the modal that opens as shown below.

Configure validation properties of slug field
Finally, switch to the Editing tab and select Slug as the editor as shown below. The options at this Editor section allow you to choose what type of editor you want to use to edit a particular field. Click Save and add field to save the
Slug
field and proceed to add the Content
field.
Configure editing properties of slug field
The third field i.e the
Content
field will be formatted as a Markdown text. Configure the Content
field as shown below. Click Create and edit field to continue.
Create Content field
Set the
Content
field to use a WYSIWYG editor by configuring the Editor to Markdown in the Editing tab as shown below. Click Save and close to save the changes to the Content
field.
Configure editing properties of content field
You have now finished creating the posts schema and its fields. To be able to add content under the posts schema, it has to be published. Click Published on the top right corner of the page to do so.

Publishing the schema
Now that the schema is published, we can add sample content to it which will be displayed once the blog is built.
To add content, click Content (1) on the sidebar and select posts (2) if it is not already selected. Click +New (3) to create a new post.

Create a new post / content
If you have some content you want to transfer to Squidex, you can paste it here. If you don't, you can manually enter some placeholders for title, slug and content, then click Save and Publish (4) to publish the content you created.

Create content and publish
Click the back icon to go back to the list of contents for the posts schema, click +New again and repeat the steps for as many posts as you like.

Now that you have created your schema on Squidex, you will proceed to set up your Next.js workspace.
Open your terminal and run the following command to install Next.js and its dependencies.
npx create-next-app squidex-blog
If you are prompted to confirm that you want to install the create-next-app package, type
y
and press enter to continue with the installation.Once Next.js is set up, run the following commands to enter the
squidex-blog
directory and start the development server.cd squidex-blog
yarn dev
The
yarn dev
command starts a Next.js development server at http://localhost:3000
. Visit htttp://localhost:3000
to see your Next.js app. You should see Welcome to Next.js on the page. You have now successfully installed Next.js and are ready to start building your blog.You will start by creating a
Layout
component that adds a header and footer to all the pages of your blog. Create a components
folder in squidex-blog
and create a layout.js
file in the squidex-blog/components
folder with the following contents:import Link from "next/link";
export default function Layout({ children }) {
return (
<div className="container">
<div className="header">
<Link href="/">
<a className="blog-heading">My Blog</a>
</Link>
</div>
{children}
<div className="footer">
<div>My Blog</div>
<div>(c) 2022 </div>
</div>
</div>
);
}
This creates a footer and a header with a link to your blog's homepage. The
children
prop allows you to embed whatever content is added into the Layout
component between the header and footer.After creating the
Layout
component, replace the contents of the squidex-blog/styles/globals.css
file with the following:html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 1em;
}
.header {
margin-top: 2em;
margin-bottom: 4em;
}
.blog-heading {
font-size: 2em;
font-weight: bold;
text-decoration: none;
color: black;
}
.footer {
margin: 4em auto;
width: fit-content;
}
.blog-item {
margin-top: 20px;
}
.blog-item-title {
font-size: 2em;
text-decoration: none;
color: black;
}
.blog-title {
font-size: 3em;
}
Now that you have modified the website's stylesheet, you will go on to modify the custom Next.js
App
component of your blog to wrap the application in a header and footer with the Layout
component you created. In Next.js, a custom app component is used to add a consistent layout to app pages and to add global CSS to your app. You can learn more about it from the Custom App page of the Next.js documentation.Modify
_app.js
in squidex-blog/pages
to have the the following content:import Layout from "../components/layout";
import "../styles/globals.css";
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
Visit
localhost:3000
if you haven't already. The development server will rebuild your blog. You will see the newly added header and footer.Now that you have added the styles and a header and footer to your application, you will proceed to set up authentication for requests to the Squidex API.
To be able to fetch data from your Squidex App, you need to create a client on Squidex.
Open the dashboard of your blog application(
squidex-blog
or whatever name you chose) from https://cloud.squidex.io. On the left sidebar, click the cog icon at the bottom left corner of the page to open settings. Under the Security section click Clients. At the top of the page, you will see a section where you can create a client. In the text box provided under Add a new client, type nextjs-blog
as the name of the client and click Add Client to create the client. Now when you scroll down the page you will notice a new nextjs-blog
client has been created. By default, this new client comes with the Editor role. In Squidex, roles define which schemas a client has access to and which operations such as reading and writing a client is authorized to do. Since you wouldn't be making any changes to your content from your blog's frontend, you will change the role of the client to Reader i.e. with only read permissions. In the dropdown for Role, select Reader.You would normally require an access token to be able to query the Squidex API, but since your blog application will only be reading data from Squidex, not editing or deleting, you will create an anonymous client. Check the Allow anonymous access checkbox to allow this client to be query published blog posts without an access token.
Now you have created a new client and allowed anonymous access to published blog posts, you will create a file containing environment variables for your Next.js app. Create a
.env.local
file in the squidex-blog
folder and add the following contentsSQUIDEX_API_URL=https://cloud.squidex.io/api/content/<YOUR_APP>/graphql
Replace
<YOUR_APP>
in the value of SQUIDEX_API_URL with the name of your app on Squidex.Next.js only loads data from the
.env.local
file once, when you start the development server. To load the environment variables you just created, you have to restart the development server. Press the combination Ctrl/CMD + C
on your terminal to stop the development server, then restart it by running:yarn dev
You will see a message in the terminal showing that environment variables have been loaded from
.env.local
. You can visit the Environment Variables page of the Next.js documentation to learn more about environment variables in Next.js. Now you will proceed to create a helper function that you will use to make GraphQL queries to Squidex.Create a
lib
folder in squidex-blog
and in this lib
folder, create a squidex.js
file. Add the following contents to the squidex-blog/lib/squidex.js
file:export default async function fetchAPI(query, { variables } = {}) {
const res = await fetch(process.env.SQUIDEX_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query,
variables,
}),
});
const json = await res.json();
if (json.errors) {
console.error(json.errors);
throw new Error("Failed to fetch API");
}
return json.data;
}
This exports a
fetchAPI
function that receives a GraphQL query and its variables as parameters, queries the Squidex API, and returns the result.You are now ready to fetch content from Squidex and display it in your Next.js blog.
In this step, you will fetch the titles and links of your blog posts and display them on the homepage of your blog.
Replace the content of
squidex-blog/pages/index.js
with this:import Head from "next/head";
export default function Home() {
return (
<div>
<Head>
<title>My blog</title>
<link rel="icon" href="/favicon.ico" />
</Head>
</div>
);
}
Open
http://localhost:3000
in your browser, if you haven't already, to continue previewing changes.At the top of
pages/index.js
, import the fetchAPI
utility from squidex-app/lib/squidex
:import fetchAPI from "../lib/squidex";
Also in
index.js
, export a getStaticProps
function defined as follows:export async function getStaticProps() {
const data = await fetchAPI(`
{
queryPostsContents {
id,
flatData {
title,
slug,
}
}
}
`);
return {
props: {
posts: data.queryPostsContents,
},
};
}
This sends a GraphQL query to your Squidex app to get the slugs and titles of all your blog posts and returns this data to your page.
In Next.js,
getStaticProps
is a function that is run at build time to fetch and store data needed to render a page. To learn more about data fetching with getStaticProps in Next.js, see the getStaticProps page of the Next.js documentation. Save the file and refresh your browser to fetch page data from the CMS.To make this data available to the
Home
page component of index.js
, pass posts as a prop to Home
in index.js:export default function Home({ posts }) {
...
}
Although the title and slugs of your blog posts are now available to
Home
component, they will not be rendered on the homepage. You will now create a BlogItem
component that will be used to display and link to all your posts.Create a
blogItem.js
file in the squidex-blog/components
folder with the following content:import Link from "next/link";
export default function BlogItem({ title, slug }) {
return (
<div className="blog-item">
<Link href={slug}>
<a className="blog-item-title">{title}</a>
</Link>
</div>
);
}
To use
BlogItem
on your homepage, import it in pages/index.js
import BlogItem from "../components/blogItem";
And in the
Home
function in index.js
, after the Head
tag, add the following:<div className="blog-post-list">
{posts.map((post) => (
<BlogItem
title={post.flatData.title}
slug={post.flatData.slug}
key={post.id}
/>
))}
</div>
This loops through your posts and creates a link to each of them.
Your completed
index.js
file will look like this:import Head from "next/head";
import fetchAPI from "../lib/squidex";
import BlogItem from "../components/blogItem";
export default function Home({ posts }) {
return (
<div>
<Head>
<title>My blog</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="blog-post-list">
{posts.map((post) => (
<BlogItem
title={post.flatData.title}
slug={post.flatData.slug}
key={post.id}
/>
))}
</div>
</div>
);
}
export async function getStaticProps() {
const data = await fetchAPI(`
{
queryPostsContents {
id,
flatData {
title,
slug,
}
}
}
`);
return {
props: {
posts: data.queryPostsContents,
},
};
}
If you look at your blog homepage in a browser, you will see that the titles of your posts are shown and they link to the page where the article can be read.
Now that you have successfully created links to your posts on the homepage, you will create a page to show the contents of the blog posts.
In the
pages
folder, create a [slug].js
file. In Next.js, a page is a React Component file in the pages directory. The component at pages/about.js, for example will be accessible at /about. A page enclosed in square brackets like [slug].js is a fallback and Next.js will show this page for all URLs that are not handled by another page, and will pass a slug
variable to the page. So when you visit a URL like /my-first-blog-post this page is used and slug
will have the value my-first-blog-post.export default function BlogPost() {
return <div>Body</div>;
}
Click one of the links on the homepage to visit the page where your blog posts will be rendered.
Since you will need to render markdown content on this page, you will install next-mdx-remote, a package that parses markdown content and renders it in your Next.js application. Stop the development server by pressing
Ctrl/Cmd + C
on your terminal and run the following command to install next-mdx-remote
:yarn add next-mdx-remote
Restart the development server after installation:
yarn dev
Import
serialize
and MDXRemote
from next-mdx-remote
at the top of [slug].js
:import { serialize } from "next-mdx-remote/serialize";
import { MDXRemote } from "next-mdx-remote";
Import
fetchAPI
at the top of [slug].js
as you will use it to fetch the contents your blog posts from Squidex:import fetchAPI from "../lib/squidex";
Export a
getStaticProps
function from [slug].js
:export async function getStaticProps({ params }) {
const data = await fetchAPI(
`
query QueryPosts($query: String!) {
queryPostsContents(filter: $query) {
id
flatData {
title
content
}
}
}
`,
{
variables: {
query: `data/Slug/iv eq '${params.slug}'`,
},
}
);
if (data.queryPostsContents.length === 0) {
return {
notFound: true,
};
}
const post = data.queryPostsContents[0].flatData;
const mdxSource = await serialize(post.content);
return {
props: {
post: post,
source: mdxSource,
},
};
}
This returns data for the blog post associated with a particular slug or returns a 404 error if no such post exists.
At this point, you may receive an error because when you use
getStaticPaths
with dynamic paths in Next.js, you are required to also export a getStaticPaths
function that tells Next.js what pages to generate at build time.Export a
getStaticPaths
function in squidex-blog/pages/index.js
:export async function getStaticPaths() {
const data = await fetchAPI(`
{
queryPostsContents {
id,
flatData {
slug
}
}
}
`);
return {
paths: data.queryPostsContents.map((post) => {
return {
params: {
slug: post.flatData.slug,
},
};
}),
fallback: false,
};
}
The Next.js development server only runs
getStaticProps
to fetch the page data when the page is initally loaded. So, to preview the changes made to getStaticProps
, you will refresh the page. When you do so, the data will be made available but since it has not been rendered, nothing will be visible. You will now proceed to render the blog post on this page.Modify the
BlogPost
function in [slug].js
:export default function BlogPost({ post, source }) {
return (
<div>
<h1 className="blog-post-title">{post.title}</h1>
<MDXRemote {...source} />
</div>
);
}
Once you save the file, the title of the blog post and the body of the post will be rendered on the page. If you did not refresh the page after adding
getStaticProps
, you may see an error because the page data has not been loaded. Refresh the page to fetch data from your Squidex application.The finished
[slug].js
file will look like this:import { serialize } from "next-mdx-remote/serialize";
import { MDXRemote } from "next-mdx-remote";
import fetchAPI from "../lib/squidex";
export default function BlogPost({ post, source }) {
return (
<div>
<h1 className="blog-post-title">{post.title}</h1>
<MDXRemote {...source} />
</div>
);
}
export async function getStaticProps({ params }) {
const data = await fetchAPI(
`
query QueryPosts($query: String!) {
queryPostsContents(filter: $query) {
id
flatData {
title
content
}
}
}
`,
{
variables: {
query: `data/Slug/iv eq '${params.slug}'`,
},
}
);
if (data.queryPostsContents.length === 0) {
return {
notFound: true,
};
}
const post = data.queryPostsContents[0].flatData;
const mdxSource = await serialize(post.content);
return {
props: {
post: post,
source: mdxSource,
},
};
}
export async function getStaticPaths() {
const data = await fetchAPI(`
{
queryPostsContents {
id,
flatData {
slug
}
}
}
`);
return {
paths: data.queryPostsContents.map((post) => {
return {
params: {
slug: post.flatData.slug,
},
};
}),
fallback: false,
};
}
Congratulations! You have now built a fully functional blog in Next.js that sources data from Squidex.
Now that you have built your blog with Next.js and Squidex, you may want to know how to deploy your blog to a live site. Check out this page on Deploying a Next.js site. You may also want to explore the Next.js Head component to see how to adding meta tags to your pages to allow search engines better understand your content.
Last modified 2mo ago