This plugin allows consuming content loaded by any of the Sourcebit source plugins (e.g., sourcebit-source-filesystem, sourcebit-source-contentful , sourcebit-source-sanity) inside Next.js pages using getStaticProps and getStaticPaths methods.
- When Next.js starts, it loads Sourcebit and runs its
fetch()
method that executes Sourcebit plugins. - A Sourcebit source plugin loads the content from a CMS or a file-system.
- The
sourcebit-target-next
plugin caches the loaded content and saves it inside the.sourcebit-nextjs-cache.json
file. - When Next.js renders a page, it calls getStaticProps and getStaticPaths methods. These methods call
the
sourcebitDataClient.getData()
method to load the content cached inside the.sourcebit-nextjs-cache.json
file and reduce it according to the needs of a particular page.
-
Install the main
sourcebit
package, thissourcebit-target-next
plugin, and one of the Sourcebit "source" plugins to fetch your content (e.g., sourcebit-source-filesystem, sourcebit-source-contentful, sourcebit-source-sanity, or create your own).npm install sourcebit sourcebit-target-next sourcebit-source-filesystem
-
Create
sourcebit.js
configuration file in the root of your Next.js project:const path = require('path'); const isDev = process.env.NODE_ENV === 'development'; module.exports = { plugins: [ /** * The `sourcebit-source-filesystem` plugin reads content files from the provided `options.sources` folders, * and generates an array of objects that is passed to the subsequent plugins. */ { module: require('sourcebit-source-filesystem'), options: { watch: isDev, sources: [ { name: 'pages', path: path.join(__dirname, 'content/pages') }, { name: 'data', path: path.join(__dirname, 'content/data') } ] } }, /** * If needed, add more plugins to transform the data */ ({ data }) => { return { ...data, objects: data.objects.map((object) => { // ... trasnform the objects return object; }) }; }, /** * The `sourcebit-target-next` plugin receives the data generated by `sourcebit-source-filesystem` plugin, * and stores it in `.sourcebit-nextjs-cache.json` file. */ { module: require('sourcebit-target-next'), options: { liveUpdate: isDev, flattenAssetUrls: true } } ] };
-
Wrap the config exported in
next.config.js
in the following way:const withSourcebit = require('sourcebit').sourcebitNext(); module.exports = withSourcebit({ // ... next.js config ... });
The
withSourcebit
function loads thesourcebit.js
config file and runssourcebit.fetch(config)
method for you, before starting the Next.js server. If you want to control the process yourself, you can load the sourcebit config and callfetch()
manually:const sourcebit = require('sourcebit'); const sourcebitConfig = require('./sourcebit.js'); sourcebit.fetch(sourcebitConfig);
-
To consume data fetched by Sourcebit and cached by
sourcebit-target-next
, import thesourcebitDataClient
fromsourcebit-target-next
and call itsgetData()
method insidegetStaticPaths
andgetStaticProps
methods. You can use one of the__metadata
properties added bysourcebit-source-filesystem
to generate props needed for a particular page.-
If a page does not use dynamic routes, then it should call the
getData()
method fromgetStaticProps
to load the cached data and compute the props specific to that page:// src/pages/about.js import { sourcebitDataClient } from 'sourcebit-target-next'; export async function getStaticProps() { const data = await sourcebitDataClient.getData(); const props = data.objects.find((object) => object?.__metadata?.relSourcePath === 'about.md') return { props }; }
-
If a page does use dynamic routes, e.g.,
src/pages/[[...slug]].js
, then it should have both thegetStaticPaths
and thegetStaticProps
methods. UsegetData()
ingetStaticPaths
to generate paths for all your pages, and then ingetStaticProps
to compute props for a particular page according to the slug parameter.// src/pages/[[...slug]].js import { sourcebitDataClient } from 'sourcebit-target-next'; export async function getStaticPaths() { const data = await sourcebitDataClient.getData(); const paths = data.objects // find objects loaded from the "pages" folder (__metadata.sourceName === "pages") .filter((object) => object?.__metadata?.sourceName === 'pages') // map the file path to the page url, e.g.: 'about.md' => '/about' .map((object) => urlPathFromFilePath(object?.__metadata?.relSourcePath)) return { paths }; } export async function getStaticProps() { const urlPath = '/' + (params?.slug || []).join('/'); const data = await sourcebitDataClient.getData(); // find the objects corresponding to the requested page const props = data.objects.find((object) => urlPathFromFilePath(object?.__metadata?.relSourcePath) === urlPath); return { props }; }
-
-
When working locally, you might want the browser to automatically reflect any content changes. To do that, wrap your pages with the following higher order component:
import { withRemoteDataUpdates } from 'sourcebit-target-next/with-remote-data-updates'; class Page extends React.Component { render() { // ... } } export default withRemoteDataUpdates(Page);
This plugin can be configured with several options:
// sourcebit.js
const isDev = process.env.NODE_ENV === 'development';
module.exports = {
plugins: [
// ...otherPlugins,
{
module: require('sourcebit-target-next'),
options: {
liveUpdate: isDev,
cacheFilePath: path.join(__dirname, '.custom-filename.json'),
disableCache: false,
flattenAssetUrls: true
}
}
]
};
liveUpdate
(boolean) A flag indicating if page should reload its data when the underlying content changes. Defaults totrue
whenNODE_ENV
is set todevelopment
.cacheFilePath
(string) A file path for cached data, default is.sourcebit-nextjs-cache.json
disableCache
(boolean) A flag indicating if plugin should produce the cache file. Note, if you set this flag to true you won't be able to usesourcebitDataClient
to consume the data and will have to callsourcebit.fetch()
for every page.flattenAssetUrls
(boolean) Some source plugins might return complex objects for linked assets. Set this flag totrue
to replace these objects with a string representing the absolute asset URL.
You can check out an example project that uses
sourcebit-source-sanity
and sourcebit-target-next
plugins to fetch the data from Sanity.io
and consume it inside Next.js pages.
Add following to your .gitignore
:
.sourcebit-nextjs-cache.json
To simplify the dynamic routing architecture and to allow greater flexibility when creating pages in Headless CMS, we advise using following pattern:
pages/[[...slug]].js
import React from 'react';
import { sourcebitDataClient } from 'sourcebit-target-next';
import { withRemoteDataUpdates } from 'sourcebit-target-next/with-remote-data-updates';
import pageLayouts from '../layouts';
class Page extends React.Component {
render() {
// every page can have different layout, pick the layout based on the "layout" property of the page
const PageLayout = pageLayouts[_.get(this.props, 'page.layout')];
return <PageLayout {...this.props} />;
}
}
export default withRemoteDataUpdates(Page);
export async function getStaticPaths() {
const data = await sourcebitDataClient.getData();
const paths = data.objects
// find objects loaded from the "pages" folder (__metadata.sourceName === "pages")
.filter((object) => object?.__metadata?.sourceName === 'pages')
// map the file path to the page url, e.g.: 'about.md' => '/about'
.map((object) => urlPathFromFilePath(object?.__metadata?.relSourcePath))
return {
paths,
fallback: false
};
}
export async function getStaticProps({ params }) {
const urlPath = '/' + (params?.slug || []).join('/');
const data = await sourcebitDataClient.getData();
// find the objects corresponding to the requested page
const props = data.objects.find((object) => urlPathFromFilePath(object?.__metadata?.relSourcePath) === urlPath);
return { props };
}