Content Modeling with File-Based Content

Learn the basics of content modeling while specifying a shape for your pages and components.

In this lesson, we'll explore the basics of structured content, while enabling basic content editing in Stackbit's visual editor.

This is a lesson within the Next.js + Markdown tutorial that requires completing all previous steps. If you're looking for more information on content modeling with Stackbit, see the conceptual guide or the file-based modeling reference.

Understanding Structured Content

As mentioned while setting up the project, Stackbit's only real requirement is that content must be structured and separated from code. Let's start with a little background on why that is necessary and beneficial . (Or skip it and jump right into the code.)

Why Stackbit Requires Structured Content

Stackbit operates in a fully declarative way. The editing experience for any given site is driven by the code and configuration provided by the site's developer(s).

To be able to provide a superior editing experience, Stackbit needs to know where the content from editable elements on the page are stored, which is why we require that you structure your content.

Benefits of Structured Content

Structured content also provides a great benefit to developers in an enhanced developer experience.

  • Components & Reusability: Separating content from code means that you can also abstract your page templates into reusable components using a component framework like React, Vue, or Svelte. This is how most modern websites are built.
  • Cleaner Code & Separation of Concerns: Your code then becomes easier to work with by being more modular and reducing the number of responsibilities for each file in your project.
  • Confidence & Type-Safety: Content being fed into components is predictable, which means you can introduce TypeScript for type-safety in your code, boosting confidence that your site will work in production.

Modeling File-Based Content

When using files as the content source, Stackbit does not inherently understand the structure of the content within the files. You can provide this schema definition using the models configuration property.

To stay organized in this tutorial, we're going to put each model definition in its own file within a .stackbit/models directory. We'll then import that definition into our main configuration file (stackbit.config.js).

Page Model

Content models can be one of three types: page, object, or data. Pages are models that represent a single webpage on your site. The collection of documents (content files) of type page is what Stackbit uses to populate the sitemap for your site.

Empty Sitemap Navigator
Empty Sitemap Navigator

We can add our home page to this sitemap by providing a simple model definition for a page model in .stackbit/models/page.js, and then importing it into stackbit.config.js.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
export const page = {
  type: 'page',
  hideContent: true,
  urlPath: '/{slug}',
  fields: [
    { name: 'title', type: 'string', required: true },
    {
      name: 'sections',
      type: 'list',
      items: {
        type: 'model',
        models: ['hero', 'stats'],
      },
    },
  ],
}

This tells Stackbit that we have two fields: title and sections. The title is just a name for the page, while sections will contain the content sent to individual components that content editors can use to build pages on your site.

Refresh your browser, then you'll see the home page in the sitemap, along with the ability to edit the page from the page editor panel.

Sitemap with Home Page
Sitemap with Home Page

Hero and Button Models

With the sections field on the page model, we specified that it was a list that could be of multiple types. If you check your Stackbit dev console, you'll see warnings about hero and stats models not being found.

Let's create the hero model and add it to our config object.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
export const hero = {
  type: 'object',
  fields: [
    { name: 'heading', type: 'string' },
    { name: 'body', type: 'markdown' },
    {
      name: 'image',
      type: 'object',
      fields: [
        { name: 'src', type: 'image' },
        { name: 'alt', type: 'string' },
      ],
    },
    { name: 'button', type: 'model', models: ['button'] },
    { name: 'theme', type: 'enum', options: ['imgLeft', 'imgRight'] },
  ],
}

Notice here that we're linking another level deeper and using a button model for the button field here. So let's add that model, too:

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
export const button = {
  type: 'object',
  fields: [
    { name: 'label', type: 'string' },
    { name: 'url', type: 'string' },
    { name: 'theme', type: 'enum', options: ['default', 'outline'] },
  ],
}

Stats Model

We also have a stats model.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
export const stats = {
  type: 'object',
  fields: [
    { name: 'heading', type: 'string' },
    { name: 'body', type: 'markdown' },
    {
      name: 'stats',
      type: 'list',
      items: {
        type: 'object',
        fields: [
          { name: 'label', type: 'string' },
          { name: 'value', type: 'string' },
        ],
      },
    },
    { name: 'theme', type: 'enum', options: ['primary', 'dark'] },
  ],
}

Like the hero model, the stats model has an embedded structured field called stats. But unlike the hero model, the stats model doesn't define this as another model, but defines the shape of the object directly.

Note: We also took this approach with the image field in hero, but only to keep this example simple. You'd likely also want to abstract image into its own model and component in a real project.

This is to demonstrate the flexibility in structuring content. How exactly you choose to structure your content is entirely up to you. In this example, button is component that is likely to used by multiple other components, while individual stat cards (called StatItem in this project) may not be, so we keep it tightly coupled to the stats model.

Editing Content Using the Page Editor

Now we're ready to start editing content!

Update Content

With all the models properly defined, you can use the page editor to adjust content within sections of the page.

Edit Hero Heading
Edit Hero Heading

Notice when you do this that the content gets updated in content/pages/index.md.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
---
title: Home Page
sections:
  - type: hero
    # ...
    heading: World's Best Cup of Coffee

Automatic Content Reload

Stackbit also listens for content changes in the content source. If you change the markdown content directly, notice that the preview in the visual editor reflects those changes. Learn more about automatic content reload.

Automatic Content Reloading
Automatic Content Reloading

Add a Section

Next, add a new section to the home page.

00:0000:00
Add Section to Page

And again, check content/pages/index.md to see how the content changed.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
---
title: Home Page
sections:
  # ...
  - type: hero
    heading: New Hero Section
    body: >
      Consectetur in magna qui ut ipsum ...
    button:
      label: Click Me
      url: /
      theme: outline
    # ...

Add a Page

And last, add a new page.

Add New Page
Add New Page

The page is blank here because we haven't added any sections to it. An upcoming lesson will cover presets, which can make this process much smoother for content editors.

You now have a new file in the content/pages directory. You can also see the new page in the sitemap.

Sitemap Navigator with About Page
Sitemap Navigator with About Page

Detailed Content-Editing Data Flow

There's a lot going on in the Stackbit dev server and API to make this all work together. You're welcome to pause and dig into the details on how Stackbit updates and retrieves content.

Otherwise, let's take content editing to the next level by introducing inline editing.