Next.js Discord

Discord Forum

Is there a way to extract some information in a `page.md` with my layout?

Unanswered
Large garden bumble bee posted this in #help-forum
Open in Discord
Large garden bumble beeOP
General idea is that I will have a layout like this:
app/
├── articles/
│   ├── my-first-article/
│   │   └── page.mdx
│   ├── my-second-article/
│   │   └── page.mdx
│   ├── layout.tsx
│   └── page.tsx
└── page.tsx

An article can be assumed to have some frontmatter data
---
title: Wow! my first article!
subtitle: Never thought I'd make it this far
slug: my-first-article
---

Now what I want to do is to render out a list of these articles on the app/articles/page.tsx, and generate static metadata from it. What I thought to do was basically use a file system:
import 'server-only';
import matter from 'gray-matter';

async function parseFile(filePath: string) {
  const content = await readfile(filePath);
  return matter(content ?? "");
}

async function getArticles() {
  const root = dirname(fileURLToPath(import.meta.url));
  const files = await walk(root, {
    maxDepth: 1,
    filter: filterByExtension(".md"),
    relativeTo: root,
  });
  const results = await Promise.allSettled(map(parseFile, files));
  return filter(where({ status: 'fulfilled' }), results);
}

function Articles() {
  const files = await getArticles();
  // ...
}

export async function generateStaticParams() {
  const files = await getArticles();
  return map(({ slug }) => ({ slug }), files);
}

Even so, I am still having a bit of trouble because I want to extract metadata for each of these md files. Like in the above case, I have no idea how to generate the metadata for that MD page
This seems like a really bad way to do it though... is this okay or is there a better way?

1 Reply

Great-tailed Grackle
Build a helper function to parse the frontmatter and content, you can see the example in a Next.js portfolio template: https://github.com/vercel/examples/blob/main/solutions/blog/app/blog/utils.ts

Or if you just need the snippet to parse the frontmatter:

function parseFrontmatter(fileContent: string) {
  let frontmatterRegex = /---\s*([\s\S]*?)\s*---/
  let match = frontmatterRegex.exec(fileContent)
  let frontMatterBlock = match![1]
  let content = fileContent.replace(frontmatterRegex, '').trim()
  let frontMatterLines = frontMatterBlock.trim().split('\n')
  let metadata: Partial<Metadata> = {}

  frontMatterLines.forEach((line) => {
    let [key, ...valueArr] = line.split(': ')
    let value = valueArr.join(': ').trim()
    value = value.replace(/^['"](.*)['"]$/, '$1') // Remove quotes
    metadata[key.trim() as keyof Metadata] = value
  })

  return { metadata: metadata as Metadata, content }
}