Document Type Fields with Contentlayer

Introduction

In this post we will discuss how to use contentlayer to define document type fields.

Core features

  • Contentlayer documentType for Article
  • Contentlayer documentType for Project
  • Contentlayer nestedType for Series
  • Contentlayer common computedFields

computedFields

computedFields.ts


import { type ComputedFields } from 'contentlayer2/source-files';

export const computedFields: ComputedFields = {
  readingTime: {
    type: 'json',
    resolve: (doc) => readingTime(doc.body.raw),
  },
  slug: {
    type: 'string',
    resolve: (doc) => doc._raw.flattenedPath.replace(/^.+?(\/)/, ''),
  },
  path: {
    type: 'string',
    resolve: (doc) => doc._raw.flattenedPath,
  },
  filePath: {
    type: 'string',
    resolve: (doc) => doc._raw.sourceFilePath,
  },
  toc: { type: 'json', resolve: (doc) => extractTocHeadings(doc.body.raw) },
};

defineNestedType for series

Series.ts

import {
  defineDocumentType,
  defineNestedType,
} from 'contentlayer2/source-files';

const Series = defineNestedType(() => ({
  name: 'Series',
  fields: {
    title: {
      type: 'string',
      required: true,
    },
    order: {
      type: 'number',
      required: true,
    },
  },
}));

defineDocumentType for article

Blog.ts


export const Blog = defineDocumentType(() => ({
  name: 'Blog',
  filePathPattern: 'article/**/*.mdx',
  contentType: 'mdx',
  fields: {
    title: { type: 'string', required: true },
    series: { type: 'nested', of: Series },
    date: { type: 'date', required: true },
    tags: { type: 'list', of: { type: 'string' }, default: [] },
    lastmod: { type: 'date' },
    featured: { type: 'boolean' },
    draft: { type: 'boolean' },
    summary: { type: 'string' },
    images: { type: 'list', of: { type: 'string' }, default: [] },
    authors: { type: 'list', of: { type: 'string' }, required: true },
    layout: { type: 'string' },
    bibliography: { type: 'string' },
    canonicalUrl: { type: 'string' },
    language: { type: 'string', required: true },
  },
  computedFields: {
    ...computedFields,
    structuredData: {
      type: 'json',
      resolve: (doc) => ({
        '@context': 'https://schema.org',
        '@type': 'BlogPosting',
        headline: doc.title,
        datePublished: doc.date,
        dateModified: doc.lastmod || doc.date,
        description: doc.summary,
        image: doc.images ? doc.images[0] : '',
      }),
    },
  },
}));

defineDocumentType for authors

Authors.ts


export const Authors = defineDocumentType(() => ({
  name: 'Authors',
  filePathPattern: 'authors/**/*.mdx',
  contentType: 'mdx',
  fields: {
    name: { type: 'string', required: true },
    default: { type: 'boolean' },
    avatar: { type: 'string' },
    occupation: { type: 'string' },
    company: { type: 'string' },
    email: { type: 'string' },
    twitter: { type: 'string' },
    linkedin: { type: 'string' },
    github: { type: 'string' },
    layout: { type: 'string' },
    portfolio: { type: 'string' },
    language: { type: 'string', required: true },
  },
  computedFields,
}));

Community

We're excited to see the community adopt Hyperse-io, raise issues, and provide feedback. Whether it's a feature request, bug report, or a project to showcase, please get involved!