Choose your preferred SSG:
Choose your preferred SSG:

Next.js starter guide

This guide will walk through the steps required to get your Next.js site built, editable and live on CloudCannon.

Next.js is one of the most popular React-based SSGs. CloudCannon makes it easy to store your content in your Git Repository and have non-developers update it. Better yet, the editing is all done in context with minimal configuration.

1: Sync your files

The first step for connecting any site is to get your file system linked to CloudCannon. You’ll need to clone your files to disk before anything else — this is the same process as if you were working locally in your dev environment.

CloudCannon offers multiple ways of connecting your files:

  1. Git syncing through GitHub, Bitbucket, GitLab and self-hosted GitLab
  2. Uploading a folder or zip of your files

It is highly recommended that you use the Git syncing option. This allows your updates on CloudCannon to push back to your repo. Your editors can then contribute to the Software Development Life Cycle (SDLC). This step is configured through an OAuth connection with the relevant partner. Check out the Connecting Your First Site guide for more information on this step.

2: Build your site

The second step for any new site is to get a successful build. This also mirrors your local development environment. On CloudCannon, select Next.js as your SSG — this will set up the defaults for Next.js.

The important things to configure here are:

  • Any required Environment Variables
  • Install Command: Command to install dependencies before build. By default this runs npm install. This is run as a line of bash and can be anything you want. A common change here is to use yarn install instead.
  • Build Command: Command to build your site. By default this runs npm run build. This is similar to the install command. An example of this build is next build && next export
  • Output Path: Path to the output folder you are building into. This is important because CloudCannon needs to know which folder to treat as the static output of your site.

Ideally your build will work the first time. If this is the case, you can move to the next step. If your build did not work the first time, be sure to read the build output — this will let you know what didn’t work.

You may find you need extra commands to complete your build. A common example is using postcss; for this you can use build hooks, adding specially named bash scripts that run at different steps in the build. For more information, see the build hook documentation.

Interlude: Your site is built

Once your site is built, you will see a big green tick on the screen. At this point, your site’s files are synced and a successful build has been run. This means you have unlocked a newly generated preview URL. In the top left corner of the screen under your site name, there is a blue link, styled adjective-noun.cloudvent.net. Clicking this link will open a new tab to the hosted site on CloudCannon. It is a helpful step at this point to check that the site looks correct. If it does, you can move on to editor configuration. If you need to update your build, this can be done in your settings.

Note

If you find the site returned a 404 on the index, you might have set the wrong output path. This can be a sign that no files were present in the configured folder.

Interlude: Editing terminology

Some SSGs, like Jekyll, Hugo, and Eleventy, have strong opinions about how to organize the content in your repo. CloudCannon can run automatic processes on these SSGs and provide basic editing out of the box. Next.js takes a less opinionated approach and allows you to decide where and how content is stored. This means that for Next.js, CloudCannon out of the box only has the source editor available.

CloudCannon has a few concepts that make organizing data easier:

  • Data: These are standalone data files. This is ideal for site configuration or files that don’t need to repeat. Data is edited by altering to a single file.
  • Collections: A folder of files that represent a repeatable data format. This is a good fit for pages, blogs, staff members, recipes, etc. Collections are edited by altering any file or adding more files.

Fortunately, these terms align with the Next.js concepts of Data Fetching. More specifically:

Next.js can implement both of these calls with either Data or Collections. With a Collection getStaticProps would pull in a single file, and getStaticPaths would pull in each file as a path. With Data, getStaticProps would pull in the whole file or an array item within it, andgetStaticPaths would return nothing or each item in an array. It is recommended that Collections are used when implementing getStaticPaths.

Note

CloudCannon does not support Server-side Rendering as our build and hosting environments are decoupled.

CloudCannon supports a set of file formats for files in Data or Collections:

  • Structured data files: JSON, YAML, TOML, CSV, TSV
  • Markup files: HTML, Markdown
  • Combination files: HTML with front matter, Markdown with front matter

Front matter refers to a section at the top of a markup file that contains structured data. CloudCannon supports JSON, YAML and TOML front matter. Markdown comes in many flavours, so be sure to checkout configuring your Markdown engine.

If you need another supported format, please contact support and we would be happy to look into it.

3: Updating Next.js to use file data

Now that we have the terminology it’s time to organize our content. Most of the Next.js documentation is weighted towards using a fetch for your getStaticProps and getStaticPaths. For example:

export async function getStaticProps(context) {
  const res = await fetch(`https://...`)
  const data = await res.json()

  return {
    props: { data }
  }
}

CloudCannon edits content stored in the local file system. This means we need to change the fetch to use an fs call instead. For example:

export async function getStaticProps(context) {
  const buffer = await fs.promises.readFile('config.json')
  const data = JSON.parse(buffer.toString('utf8'))

  return {
    props: { data }
  }
}

Next.js documentation refers to this under Data Fetching: Reading files. The same process will need to happen for any calls to getStaticPaths. For our templates, we have created a new helper library to allow for combination markdown and front matter collections:

import { readdir, readFile } from 'fs/promises';
import path from 'path';
import matter from 'gray-matter';
import MarkdownIt from 'markdown-it';
import next from 'next';

const md = new MarkdownIt({ html: true });
const collectionsDirectory = path.join(process.cwd(), 'content');

export async function getCollectionSlugs(collection) {
	const fileNames = await readdir(path.join(collectionsDirectory, collection));
	return fileNames.map((fileName) => ({
		params: {
			slug: path.basename(fileName, path.extname(fileName))
		}
	}));
}

export async function getCollection(collection, options = {}) {
	const fileNames = await readdir(path.join(collectionsDirectory, collection));

	const collectionItems = await Promise.all(await fileNames.reduce(async (memo, fileName) => {
		const slug = path.basename(fileName, path.extname(fileName));

		if (!slug.startsWith('_')) {
			const item = await getCollectionItem(collection, slug, options);
			return [...await memo, item];
		}

		return memo;
	}, []));

	if (options.sortKey) {
		return collectionItems.sort((a, b) => {
			if (a[options.sortKey] === b[options.sortKey]) {
				return 0;
			}

			return a[options.sortKey] > b[options.sortKey] ? -1 : 1;
		});
	}

	return collectionItems;
}

export async function getCollectionItem(collection, slug, options = {}) {
	const fullPath = path.join(collectionsDirectory, collection, `${slug}.md`);
	const fileContents = await readFile(fullPath, 'utf8');
	const parsed = matter(fileContents);
	const contentHtml = md.render(parsed.content);

	if (options.excerpt && !parsed.excerptHtml) {
		parsed.data.excerptHtml = md.renderInline(parsed.content.split('\n').slice(1, 2).join(' '));
	}

	return {
		...parsed.data,
		slug,
		contentHtml
	};
}
export async function getNextCollectionItem(collection, slug, options = {}) {
	const collectionItems = await getCollection(collection, options)

	const index = collectionItems.map(function(e) { return e.slug; }).indexOf(slug);

	return collectionItems[index+1];
}

This can be used in the following way:

import ClientLayout from '../../components/layouts/client';
import { getCollectionSlugs, getCollectionItem } from '../../lib/collections';

export default function Post({ page, portfolio }) {
	return (
		<ClientLayout page={page} portfolio={portfolio}/>
	);
}

export async function getStaticPaths() {
	const slugs = await getCollectionSlugs('clients');
	const ignored = {
		_defaults: true
	};
	return {
		paths: slugs.filter(({ params }) => !ignored[params.slug]),
		fallback: false
	};
}

export async function getStaticProps({ params }) {
	const page = await getCollectionItem('clients', params.slug);
	const portfolio = await getCollectionItem('pages', 'portfolio');
	return {
		props: {
			page: JSON.parse(JSON.stringify(page)),
			portfolio: JSON.parse(JSON.stringify(portfolio))
		}
	};
}
Note

This library will be turned into an NPM package in the future, which will allow for simpler documentation here.

For more usage examples, we recommend taking a look at our Urban Next.js template.

4: Tell CloudCannon about the data

With all of our content organized into files, we need to tell CloudCannon how to read our structure. For this we use the CloudCannon reader, an open-source NPM library used to integrate editing.

Check out the documentation and define your collections. Once you have pushed your changes to CloudCannon, a new build will make new interfaces available. You will be able to:

  • Add, remove, edit and rename any collection item
  • Edit any data file

You will have numerous editing options available to you:

  • Source Editor: edit your code directly within CloudCannon in a familiar-looking editor.
  • Data Editor: Edit your data and front matter in a recursive form full of customizable inputs.
    • Add, remove, clone, rearrange and edit arrays
    • Open objects and edit content to any depth
    • Change data with date, color, number, URL, and more inputs
  • Content Editor: Edit your markdown files and data at the same time. A big content block for your markdown and an embedded data editor on the side. This is perfect for a combination of markdown and front matter files.
  • Visual Editor: A combination editor with your live site on one side and the data editor on the other side. The rest of this tutorial will focus on integrating the two sides more.

5: Implement visual editing

Visual editing on CloudCannon can be broken down into two separate parts:

  1. Previews: When data is changed in a data editor, the new data is sent to the page props. This allows the editor to see the changes as they make them. Since React does most of the work for us here, we can add a prebuilt higher-order component in a few minutes.
  2. Bindings: Tagging elements on the page tells CloudCannon where the data came from. CloudCannon can then attach a UI to those parts directly on the page. This frees the editor from using the Data Editor in the sidebar.

The final result is something intuitive and easy to teach.

6: Rejoice & Share

That’s it for the base Next.js setup. Your Data Editor will still be using the automatic configuration. If you find that some inputs could be improved, be sure to check out how to configure inputs.

Now that you are happy with the experience as a developer, you can share it with your team or clients. If you have an internal team, see team sharing. If you have a client, see client sharing. If you need more information, check out our SSO/SAML support.

If you experience any issues with your Next.js build, don’t hesitate to contact our support team.

Was this article helpful? or Suggest an improvement >