Collections

Collections are a way to organize and query file-system data in renoun. They are a powerful tool that allows you to define a schema for file exports and query those exports using a simple API.

Creating a Collection

Let’s walk through an example of creating a collection for a blog. We’ll define a PostsCollection that targets all MDX files within the posts directory using the filePattern field that accepts a minimatch pattern:

collections.ts
import { Collection } from 'renoun/collections'

export const PostsCollection = new Collection({
  filePattern: 'posts/*.mdx',
})

Configuring Collections

You can specify a baseDirectory to trim the beginning of the generated source paths up to that point:

collections.ts
import { Collection } from 'renoun/collections'

export const PostsCollection = new Collection({
  filePattern: '*.mdx',
  baseDirectory: 'posts',
})

In this example, the PostsCollection will target all MDX files within the posts directory and generate paths relative to the posts directory. This means a file system path like posts/how-to-build-a-button-component.mdx will be transformed into the URL path /how-to-build-a-button-component. Notice the baseDirectory is trimmed from the source path.

If you want to customize the source path, you can set the basePath option which will prepend a path to the final generated path:

collections.ts
import { Collection } from 'renoun/collections'

export const PostsCollection = new Collection({
  filePattern: '*.mdx',
  baseDirectory: 'posts',
  basePath: 'blog',
})

The basePath option is useful if the targeted directory is a different name then what you want for the route. Note, this option does not affect the path used in getSource to query sources.

Collection Sources

Sources are an abstraction of the file system that represent a single file or directory in a collection. You can query sources for descendant sources, exports, siblings, and other metadata.

To retrieve a source, you can use the getSource method with a path relative to the collection’s baseDirectory:

posts.tsx
import { PostsCollection } from './collections'

export default async function Posts() {
  const source = await PostsCollection.getSource(
    'how-to-build-a-button-component'
  )

  if (!source) {
    return <div>Post not found</div>
  }

  return <div>{source.getName()}</div>
}

File Export Types

To define the shape of the file exports, you can pass a generic type to the Collection constructor. This type will be used to type-check the exports of the files in the collection.

Since we’re working with MDX, we can use the MDXContent type from the renoun/mdx package to define the shape of the default export. We’ll also add types for the frontmatter export we expect to be defined in the MDX files:

collections.ts
import { Collection, type FileSystemSource } from 'renoun/collections'
import type { MDXContent } from 'renoun/mdx'

interface PostsSchema {
  default: MDXContent
  frontmatter: {
    title: string
    description: string
  }
}

export const PostsCollection = new Collection<PostsSchema>({
  filePattern: '*.mdx',
  baseDirectory: 'posts',
  basePath: 'blog',
})

This will ensure that the default export of each MDX file in the posts directory is of type MDXContent, and that the frontmatter export is an object with title and description properties.

Schema Validation

While types are useful for type-checking, you can also define a schema for the collection. A schema is a set of rules that define the structure of the file exports and provide useful error handling.

You can use the schema field to define a schema for the expected file exports in a collection. Using a library like Zod can help you define schemas in a type-safe way:

import { Collection } from 'renoun/collections'
import type { MDXContent } from 'renoun/mdx'
import { z } from 'zod'

const frontmatterSchema = z.object({
  title: z.string(),
  description: z.string(),
})

interface PostsSchema {
  default: MDXContent
  frontmatter: z.infer<typeof frontmatterSchema>
}

export const PostsCollection = new Collection<PostsSchema>({
  filePattern: '*.mdx',
  baseDirectory: 'posts',
  basePath: 'blog',
  schema: {
    frontmatter: frontmatterSchema.parse,
  },
})

Now we’ll validate the front matter of each MDX file in the posts directory using the frontmatterSchema. This ensures that each file adheres to the schema and provides type safety when accessing the front matter.

See the Zod and Valibot guides for more information on how to use schemas with collections.

Querying Sources

Once you’ve configured your collections, you can query the targeted files using the getSource and getSources methods. These methods return Source instances that represent a single file or directory in the collection.

To start, we’ll generate a set of navigation links for all of the posts in the PostsCollection using the getSources method. Each Source has additional helper methods, such as getPath that returns a URL-friendly path of the source relative to the collection’s base directory:

posts/index.tsx
import { PostsCollection } from '../collections'

export default async function Posts() {
  const sources = await PostsCollection.getSources()

  return (
    <ul>
      {sources.map((source) => (
        <li key={source.getPath()}>
          <a href={source.getPath()}>{source.getName()}</a>
        </li>
      ))}
    </ul>
  )
}

We can now generate a page for individual posts using a slug parameter to retrieve the source for a specific MDX file in the posts directory:

posts/post.tsx
import { PostsCollection } from '../collections'

export default async function Post({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const post = await PostsCollection.getSource((await params).slug)

  if (!post) {
    return <div>Post not found</div>
  }

  const Content = await post.getExport('default').getValue()

  return <Content />
}

This was a simple example of how to query and render the contents of MDX files using collections. Collections are not limited to MDX files and can be used with any file type your bundler is capable of importing.

Composite Collections

In addition to creating individual collections, renoun also allows you to define composite collections. A composite collection is a combination of multiple collections, allowing you to treat them as a single entity. This can be useful when you want to query across different directories or file patterns while maintaining a unified interface.

Creating a Composite Collection

Let’s say you have two collections, one for blog posts, and another for components. Using a composite collection, you can combine these into a single collection that can be queried as if it were one:

import { Collection, CompositeCollection } from 'renoun/collections'

const PostsCollection = new Collection({
  filePattern: '*.mdx',
  baseDirectory: 'posts',
})

const ComponentsCollection = new Collection({
  filePattern: '**/*.{ts,tsx}',
  baseDirectory: 'src/components',
})

const AllCollections = new CompositeCollection(
  PostsCollection,
  ComponentsCollection
)

With this setup, AllCollections allows you to query across both PostsCollection and ComponentsCollection seamlessly.

Querying Across Collections

When retrieving a source and querying for siblings, composite collections will account for all sources across the collections it comprises:

const source = AllCollections.getSource(
  'posts/how-to-build-a-button-component'
)!
const [previousSource, nextSource] = await source.getSiblings()

Here, source.getSiblings() will return the sources from both PostsCollection and ComponentsCollection as a combined set.

Narrowing Source Types

Collections provide a type guard to check if a source belongs to a specific collection within a composite collection. You can use the <Collection>.hasSource method to safely narrow the type of a source when working with composite collections:

if (ComponentsCollection.hasSource(nextSource)) {
  // nextSource is now typed as a ComponentsCollection source
}

This type guard ensures that you’re working with the correct source type within a composite collection, allowing you to access schema-specific exports.

Conclusion

Collections can be used to generate static pages, create navigations, site maps and much more. At their core, they abstract files and directories into either a CollectionSource, FileSystemSource, or ExportSource allowing you to analyze and render them programmatically.

Explore more ways to utilize collections by visiting the recipes page for practical examples.

API Reference

FilePatterns

FilePatterns<Extension>

`${string}${Extension}`

`${string}${Extension}${string}`

BaseSource

BaseSource
Properties

getPath *

() => string

The full path to the source formatted to be URL-friendly, taking the collection baseDirectory and basePath configuration into account.

getPathSegments *

() => string[]

An array of path segments to the source excluding the collection basePath if configured.

getFileSystemPath *

() => string

The file path to the source in the file system.

getEditPath *

() => string

The path to the source on the local filesystem in development and the git repository in production if configured.

SourceProvider

SourceProvider<Exports>
Properties

getSource *

(path?: string | string[]) => Promise<FileSystemSource<Exports> | undefined>

Retrieves a source in the immediate directory or sub-directory by its path.

path

string | Array<string> | undefined

getSources *

<Depth extends number>(options?: { depth?: PositiveIntegerOrInfinity<Depth>; }) => Promise<FileSystemSource<Exports>[]>

Retrieves sources in the immediate directory and possibly sub-directories based on the provided depth. Defaults to a depth of Infinity which will return all sources.

options

{ depth?: PositiveIntegerOrInfinity<Depth>; } | undefined

ExportSource

ExportSource<Value>
Properties

getName *

() => string

The name of the exported source. If the default export name cannot be derived, the file name will be used.

getType *

(filter?: SymbolFilter) => Promise<ResolvedType | undefined>

The resolved type of the exported source based on the TypeScript type if it exists.

filter

SymbolFilter | undefined

getTitle *

() => string

The name of the exported source formatted as a title.

getDescription *

() => string | undefined

The description of the exported source based on the JSDoc comment if it exists.

getTags *

() => { tagName: string; text?: string; }[] | undefined

The tags of the exported source based on the JSDoc comment if it exists.

getSlug *

() => string

The URL-friendly slug of the export name.

getText *

() => string

A text representation of the exported source if it is statically analyzable.

getValue *

() => Promise<Value>

The runtime value of the export loaded from the dynamic import generated at the related collection's call site. Note, any side-effects in modules of targeted files will be run.

getEnvironment *

() => "server" | "client" | "isomorphic" | "unknown"

The execution environment of the export source.

getPosition *

() => DeclarationPosition

The lines and columns where the export starts and ends.

getSiblings *

() => Promise<[previous?: ExportSource<Value>, next?: ExportSource<Value>]>

The previous and next export sources within the same file.

isMainExport *

() => boolean

Whether the export is considered the main export of the file based on the name matching the file name or directory name.

getPath *

() => string

The full path to the source formatted to be URL-friendly, taking the collection baseDirectory and basePath configuration into account.

getPathSegments *

() => string[]

An array of path segments to the source excluding the collection basePath if configured.

getFileSystemPath *

() => string

The file path to the source in the file system.

getEditPath *

() => string

The path to the source on the local filesystem in development and the git repository in production if configured.

FileSystemSource

FileSystemSource<Exports>
Properties

getName *

() => string

The base file name or directory name.

getTitle *

() => string

The file name formatted as a title.

getOrder *

() => string

Order of the source in the collection based on its position in the file system.

getDepth *

() => number

Depth of source starting from the collection.

getCreatedAt *

() => Promise<Date | undefined>

Date the source was first created.

getUpdatedAt *

() => Promise<Date | undefined>

Date the source was last updated.

getAuthors *

() => Promise<string[]>

Authors who have contributed to the source.

getSiblings *

(options?: { depth?: number; }) => Promise<[previous?: FileSystemSource<Exports>, next?: FileSystemSource<Exports>]>

The previous and next sources in the collection if they exist. Defaults to a depth of Infinity which considers all descendants.

options

{ depth?: number; } | undefined

getExport *

<Name extends keyof Exports>(name: Name) => ExportSource<Exports[Name]>

A single named export source of the file.

name *

Name

getMainExport *

() => ExportSource<Exports[keyof Exports]> | undefined

The main export source of the file based on the file name or directory name.

getExports *

() => ExportSource<Exports[keyof Exports]>[]

All exported sources of the file.

isFile *

() => boolean

If the source is a file.

isDirectory *

() => boolean

If the source is a directory.

getPath *

() => string

The full path to the source formatted to be URL-friendly, taking the collection baseDirectory and basePath configuration into account.

getPathSegments *

() => string[]

An array of path segments to the source excluding the collection basePath if configured.

getFileSystemPath *

() => string

The file path to the source in the file system.

getEditPath *

() => string

The path to the source on the local filesystem in development and the git repository in production if configured.

getSource *

(path?: string | string[]) => Promise<FileSystemSource<Exports> | undefined>

Retrieves a source in the immediate directory or sub-directory by its path.

path

string | Array<string> | undefined

getSources *

<Depth extends number>(options?: { depth?: PositiveIntegerOrInfinity<Depth> | undefined; } | undefined) => Promise<Array<FileSystemSource<Exports>>>

Retrieves sources in the immediate directory and possibly sub-directories based on the provided depth. Defaults to a depth of Infinity which will return all sources.

options

{ depth?: PositiveIntegerOrInfinity<Depth> | undefined; } | undefined

CollectionSource

CollectionSource<Exports>
Properties

getPath *

() => string

The full path to the source formatted to be URL-friendly, taking the collection baseDirectory and basePath configuration into account.

getSource *

(path?: string | string[]) => Promise<FileSystemSource<Exports> | undefined>

Retrieves a source in the immediate directory or sub-directory by its path.

path

string | Array<string> | undefined

getSources *

<Depth extends number>(options?: { depth?: PositiveIntegerOrInfinity<Depth> | undefined; } | undefined) => Promise<Array<FileSystemSource<Exports>>>

Retrieves sources in the immediate directory and possibly sub-directories based on the provided depth. Defaults to a depth of Infinity which will return all sources.

options

{ depth?: PositiveIntegerOrInfinity<Depth> | undefined; } | undefined

hasSource *

(source: FileSystemSource<any> | undefined) => source is FileSystemSource<Exports>

source *

FileSystemSource<any> | undefined

CollectionOptions

CollectionOptions<Exports>
Properties

filePattern *

string

The file pattern used to match source files. Accepts a minimatch file pattern.

baseDirectory

string | undefined

The base directory path to trim from calculated paths.

The path must match a portion of the base directory structure of the targeted file pattern. For example, if the file pattern is src/components/*.{ts,tsx} and the baseDirectory is src/components, the path will only include the file name.

basePath

string | undefined

The base pathname used when calculating navigation paths. This includes everything after the hostname (e.g. /docs in https://renoun.com/docs).

tsConfigFilePath

string | undefined

The path to the TypeScript config file.

filter

((source: FileSystemSource<Exports> | ExportSource<any>) => boolean) | undefined

A filter function to only include specific file system sources. If tsConfigFilePath is defined, all files matching paths in ignore will always be filtered out.

sort

((a: FileSystemSource<Exports>, b: FileSystemSource<Exports>) => Promise<number>) | undefined

A custom sort function for ordering file system sources.

schema

{ [Name in keyof Exports]?: ((value: Exports[Name]) => Exports[Name]) | undefined; } | undefined

Validate and transform exported values from source files.

Collection

Collection<AllExports>

Creates a collection of file system sources based on a file pattern.

Constructors

<AllExports>(options: CollectionOptions<AllExports>, getImport?: GetImport | Array<GetImport> | undefined) => Collection<AllExports>

Methods

getPath

() => string

getDepth

() => number

getSource

(path?: string | string[]) => Promise<FileSystemSource<AllExports> | undefined>

getSources

({ depth }?: { depth?: number; }) => Promise<Array<FileSystemSource<AllExports>>>

hasSource

(source: FileSystemSource<any> | undefined) => source is FileSystemSource<AllExports>
Properties

options

CollectionOptions<AllExports>

CompositeCollection

CompositeCollection<Collections>

Combines multiple collections into a single source provider that can be queried together. This is useful for creating feeds or navigations that span multiple collections.

Constructors

<Collections extends Array<CollectionSource<any>>>(collections: Collections) => CompositeCollection<Collections>

Methods

getSource

(path?: string | string[]) => Promise<FileSystemSourceUnion<Collections> | undefined>

getSources

({ depth }?: { depth?: number; }) => Promise<FileSystemSourceUnion<Collections>[]>

isExportSource

(source: unknown) => source is ExportSource<any>
Parameters

source *

unknown
Returns
boolean

isFileSystemSource

(source: unknown) => source is FileSystemSource<any>
Parameters

source *

unknown
Returns
boolean

isCollectionSource

(source: unknown) => source is CollectionSource<any>
Parameters

source *

unknown
Returns
boolean