File System
File System
The File System utilities offer a way to organize and query file-system data in renoun. It is a powerful tool that allows you to define a schema for file exports and query those exports using a simple API.
To get started with the File System API, instantiate the Directory
class to target a set of files and directories from the file system. You can then use the getEntry
/ getDirectory
/ getFile
methods to query a specific descendant file or directory:
import { Directory } from 'renoun/file-system'
const posts = new Directory({
path: 'posts',
loader: {
mdx: (path) => import(`./posts/${path}.mdx`),
},
})
Here we are creating a new Directory
instance that targets the posts
directory relative to the working directory. We are also specifying a loader for the mdx
file extension that will be used to load the file contents using the bundler.
Querying file system entries
import { Directory } from 'renoun/file-system'
const posts = new Directory({
path: 'posts',
loader: {
mdx: (path) => import(`./posts/${path}.mdx`),
},
})
export default async function Page({ slug }: { slug: string }) {
const post = await posts.getFile(slug, 'mdx')
const Content = await post.getExportValue('default')
return <Content />
}
You can query the entries within the directory to help with generating navigations and index pages. For example, we can include only mdx
file extensions to generate an index page of links to all posts using the getEntries
method:
import { Directory, withSchema } from 'renoun/file-system'
interface PostType {
frontmatter: {
title: string
date: Date
}
}
const posts = new Directory({
path: 'posts',
include: '*.mdx',
loader: {
mdx: withSchema<PostType>((path) => import(`./posts/${path}.mdx`)),
},
})
export default async function Page() {
const allPosts = await posts.getEntries()
return (
<>
<h1>Blog</h1>
<ul>
{allPosts.map(async (post) => {
const pathname = post.getPathname()
const frontmatter = await post.getExportValue('frontmatter')
return (
<li key={pathname}>
<a href={pathname}>{frontmatter.title}</a>
</li>
)
})}
</ul>
</>
)
}
Type checking file exports
To improve type safety, you can utilize the withSchema
helper to specify the schema for the file’s exports:
import { Directory, withSchema } from 'renoun/file-system'
interface PostType {
frontmatter: {
title: string
date: Date
}
}
const posts = new Directory({
path: 'posts',
loader: {
mdx: withSchema<PostType>((path) => import(`./posts/${path}.mdx`)),
},
})
Now when we call JavaScript#getExportValue
and JavaScriptExport#getRuntimeValue
we will have stronger type checking and autocomplete.
Schema Validation
You can also apply schema validation using libraries that follow the Standard Schema Spec like Zod, Valibot, or Arktype to ensure file exports conform to a specific schema:
import { Directory, withSchema } from 'renoun/file-system'
import { z } from 'zod'
const posts = new Directory({
path: 'posts',
loader: {
mdx: withSchema(
{
frontmatter: z.object({
title: z.string(),
date: z.date(),
}),
},
(path) => import(`./posts/${path}.mdx`)
),
},
})
Alternatively, you can define a schema yourself using both TypeScript types and custom validation functions:
import { Directory, withSchema } from 'renoun/file-system'
interface PostType {
frontmatter: {
title: string
date: Date
}
}
const posts = new Directory({
path: 'posts',
loader: {
mdx: withSchema<PostType>(
{
frontmatter: (value) => {
if (typeof value.title !== 'string') {
throw new Error('Title is required')
}
if (!(value.date instanceof Date)) {
throw new Error('Date is required')
}
return value
},
},
(path) => import(`./posts/${path}.mdx`)
),
},
})
The file system utilities are not limited to MDX files and can be used with any file type. By organizing content and source code into structured collections, you can easily generate static pages and manage complex routing and navigations.