Skip to content

HMR breaks with MDX frontmatter #14082

@nikolailehbrink

Description

@nikolailehbrink

Reproduction

  1. Go to https://stackblitz.com/edit/github-8mzcjeeh?file=app%2Froutes%2Fblog%2Fpost.mdx
  2. Run the project
  3. Update the blog/post.mdx file
  4. See that HMR doesn't work because of the consistent-components-exports
    Image

System Info

System:
    OS: Windows 11 10.0.26100
    CPU: (16) x64 AMD Ryzen 7 5800X 8-Core Processor
    Memory: 46.29 GB / 63.93 GB
  Binaries:
    Node: 22.14.0 - C:\nvm4w\nodejs\node.EXE
    Yarn: 1.22.22 - C:\nvm4w\nodejs\yarn.CMD
    npm: 10.9.2 - C:\nvm4w\nodejs\npm.CMD
    pnpm: 10.12.1 - C:\nvm4w\nodejs\pnpm.CMD
  Browsers:
    Edge: Chromium (133.0.3065.59)
    Internet Explorer: 11.0.26100.1882
  npmPackages:
    @react-router/dev: ^7.7.0 => 7.7.0
    @react-router/node: ^7.7.0 => 7.7.0
    @react-router/serve: ^7.7.0 => 7.7.0
    react-router: ^7.7.0 => 7.7.0
    vite: ^6.3.3 => 6.3.5

Used Package Manager

npm

Expected Behavior

HMR doesn't break when saving a .mdx file with frontmatter.

Actual Behavior

HMR breaks when editing mdx files with frontmatter.

I understand why this is happening — the error message from React Refresh makes it clear. When using remark-frontmatter with @mdx-js/rollup, it adds a frontmatter export. Since this isn't included in the CLIENT_NON_COMPONENT_EXPORTS array inside the @react-router/dev/vite Vite plugin, HMR is disabled because React Refresh treats it as a second component export.

I saw that you’ve already worked around this issue for all the other exports like meta, handle, links, etc., via this array:

const CLIENT_NON_COMPONENT_EXPORTS = [
  "clientAction",
  "clientLoader",
  "unstable_clientMiddleware",
  "handle",
  "meta",
  "links",
  "shouldRevalidate",
];

Now, I could patch the plugin locally and add "frontmatter" to this list — and it would work — but I’d expect support for frontmatter to be handled by the framework itself, especially given how common it is in MDX workflows.

I also considered setting the name option in remark-mdx-frontmatter to "handle", which technically works — but then I can't export an actual handle anymore without causing a duplicate export error.

// vite.config.ts
mdx({
  remarkPlugins: [
    remarkFrontmatter,
    [remarkMdxFrontmatter, { name: "handle" }],
  ],
}),

Also, @brookslybrand — not sure how HMR with frontmatter worked for you in this video, but with the same setup in RR7 it doesn’t seem to work out of the box when frontmatter is involved.

Suggestion

Would it make sense to either:

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions