If you've worked with Next.js before, you are probably familiar with how its convention-based routing works: you create a hierarchy of files and directories under the pages directory, and that hierarchy reflects the desired URL structure of the site. As Next.js projects evolve, this hierarchy gets more complex.

This is well and nice for websites with a hard-coded URL mapping. For a Stackbit project, you'll need something better: a way to fully generate the webpage structure from content files, and then delegate all rendering work to the component registered to handle the content type of each page.

All that, with minimal code and no elaborate and brittle switch or if statements in your theme.

The Catch All

To achieve the noble goal above, the theme defines only one route file - one that is set up to catch requests to any page URL.

Have a look in the src/pages/ directory and you'll find the file [[...slug]].js. In Next.js terms, this is an optional catch all route which will capture requests to all URLs including '/'.

This file is fairly short, so here's almost all the code in it:

import React from 'react'
import { sourcebitDataClient } from 'sourcebit-target-next'
import { withRemoteDataUpdates } from 'sourcebit-target-next/with-remote-data-updates'
import { getComponent } from '../components/components-registry'
import { resolveStaticProps } from '../utils/static-props-resolvers'
import { resolveStaticPaths } from '../utils/static-paths-resolvers'

function Page(props) {
  const { page, site } = props
  const { layout } = page

  const PageLayout = getComponent(layout)
  return <PageLayout page={page} site={site} />
}

export async function getStaticPaths() {
  const data = await sourcebitDataClient.getData()
  const paths = resolveStaticPaths(data)
  return { paths, fallback: false }
}

export async function getStaticProps({ params }) {
  const data = await sourcebitDataClient.getData()
  const urlPath = '/' + (params.slug || []).join('/')
  const props = await resolveStaticProps(urlPath, data)
  return { props }
}

export default withRemoteDataUpdates(Page)

Here's a refresher on how Next.js will run this code:

First, it calls getStaticPaths() to get a list of URLs to statically build with this route. In our case, that will be all pages. The implementation here delegates most of that work to Sourcebit (see previous chapter), and the rest to a utility function that knows what extra data specific page types should need.

Then, for each of these URLs: Next calls getStaticProps() to get the specific arguments (a.k.a props) it should pass to the rendering function, and then calls that function.

In the above example, that function being called is Page(). This is a functional React component that returns the full virtual DOM tree for the whole page.

Now, all that's left for Next.js to do then is to generate the HTML from the virtual DOM, and store it as a file ready for static serving!

Getting the Right Component

In the above example, note that Page() has no knowledge of any content types in our project. Instead, it has a very simple job:

  1. Call getComponent() with the page's content type (i.e. a model name) which is stored in its layout property. This call returns the concrete functional component registered to render that type.
  2. Return an instance of that component, initialized with the page content and site-wide configuration as its arguments.

With that, the route function has performed its modest but essential part. It is the anonymous component returned by getComponent() that will actually handle the page rendering. But it won't do so all by itself as well: it will also use getComponent() internally to delegate some of the work. To learn more on that topic, it's strongly recommended to read Nesting Components Effectively.

This is where our tour of the theme ends. If you're unsure about how components work, how models are defined, or any other topic, please explore the rest of the conceptual guides.