hashicorp / next-mdx-remote

Load MDX content from anywhere
Mozilla Public License 2.0
2.6k stars 139 forks source link

Export alternative to `compileMDX` for RSC which only returns frontmatter #424

Open Svish opened 8 months ago

Svish commented 8 months ago

I have successfully created a blog using the new next-mdx-remote/rsc API, and it works pretty great.

My only issue so far, is that gathering the frontmatter from all the blog posts (for indexes, lists, etc) using compileMDX is rather slow.

It would be great if there was an alternative export which worked the same as compileMDX, but only returned the frontmatter and (importantly) skipped all the actual MDX work.

I'm guessing I could use a different dependency to just strip out that frontmatter myself, but would be great if it could be a builtin export from this library so I can trust that it's done the same way in both cases. That is, the frontmatter returned from compileMDX and extractFrontmatter(or whatever) should always be the same and use the same code behind.

talatkuyuk commented 8 months ago

I will share soon a forked and updated version of next-mdx-remote. And you will get the function you need from the new project.

Edit: Here is the utility function to get the frontmatter without compiling the content

Utility getFrontmatter

The next-mdx-remote-client exports one utility getFrontmatter which is for getting the frontmatter without compiling the source. You can get the fronmatter and the stripped source by using the getFrontmatter which employs the same frontmatter extractor vfile-matter used within the package.

import { getFrontmatter } from "next-mdx-remote-client/utils";

const { frontmatter, strippedSource } = getFrontmatter<TFrontmatter>(source);

If you provide a generic type parameter, it ensures the frontmatter gets the type, otherwise Record<string, unknown> by default.

If there is no frontmatter in the source, the frontmatter will be empty object {}.

[!IMPORTANT] The subpath "next-mdx-remote-client/utils" is isolated from other features of the package and it does cost minimum. So, anyone can use next-mdx-remote-client/utils while using next-mdx-remote.

KitsonBroadhurst commented 4 months ago

Running into the same issue, but reluctant to add an alternative package for the time being.

Is there any plan for next-mdx-remote to support loading frontmatter only?

Svish commented 4 months ago

I ended up doing it myself, which was pretty simple using vfile and vfile-matter, which are dependencies already included by next-mdx-remote:

import fs from 'fs/promises'
import path from 'path'

import { VFile } from 'vfile'
import { matter } from 'vfile-matter'

async function parseMdx(filepath: string) {
  const source = await fs.readFile(filepath, {
    encoding: 'utf8',
    flag: 'r',
  })
  const vfile = new VFile(source)

  const slug = path.basename(filepath, '.mdx')
  const meta = matter(vfile, { strip: true }).data.matter

  return {
    meta,
    slug,
    source: String(vfile),
  }
}
talatkuyuk commented 3 months ago

I ended up doing it myself, which was pretty simple using vfile and vfile-matter, which are dependencies already included by next-mdx-remote:

import fs from 'fs/promises'
import path from 'path'

import { VFile } from 'vfile'
import { matter } from 'vfile-matter'

async function parseMdx(filepath: string) {
  const source = await fs.readFile(filepath, {
    encoding: 'utf8',
    flag: 'r',
  })
  const vfile = new VFile(source)

  const slug = path.basename(filepath, '.mdx')
  const meta = matter(vfile, { strip: true }).data.matter

  return {
    meta,
    slug,
    source: String(vfile),
  }
}

Hi @Svish, apart from getting slug of a file, getFrontmatter from next-mdx-remote-client/utils does exactly what your parseMdx does using the same packages vfile and vfile-matter.

One beneficial of getFrontmatter though is to get the Frontmatter type via type parameter.

The subpath "next-mdx-remote-client/utils" is isolated from other features of the package and it does cost minimum. So anyone can use next-mdx-remote-client/utils while using next-mdx-remote.

My additional advice is that let parseMdx do nothing about slug, keep parseMdx do one job for single responsibility, it will ease the tests.

Svish commented 3 months ago

I ended up doing it myself, which was pretty simple using vfile and vfile-matter, which are dependencies already included by next-mdx-remote:

import fs from 'fs/promises'
import path from 'path'

import { VFile } from 'vfile'
import { matter } from 'vfile-matter'

async function parseMdx(filepath: string) {
  const source = await fs.readFile(filepath, {
    encoding: 'utf8',
    flag: 'r',
  })
  const vfile = new VFile(source)

  const slug = path.basename(filepath, '.mdx')
  const meta = matter(vfile, { strip: true }).data.matter

  return {
    meta,
    slug,
    source: String(vfile),
  }
}

Hi @Svish, apart from getting slug of a file, getFrontmatter from next-mdx-remote-client/utils does exactly what your parseMdx does using the same packages vfile and vfile-matter.

One beneficial of getFrontmatter though is to get the Frontmatter type via type parameter.

The subpath "next-mdx-remote-client/utils" is isolated from other features of the package and it does cost minimum. So anyone can use next-mdx-remote-client/utils while using next-mdx-remote.

My additional advice is that let parseMdx do nothing about slug, keep parseMdx do one job for single responsibility, it will ease the tests.

Maybe that function should be documented better then?

Either way, I no longer need it, hehe. Type parameter has no relevance for me because in my actual code I actually use a zod schema to parse and validate the frontmatter.

That code is not my actual code, it's just the boiled down stuff relevant here.