Open HMilbradt opened 1 year ago
If anyone stumbles upon this, I ended up working around this by instead using contentlayer and it's local filesystem. You give up some of the flexiblity of direct imports in your MDX files, but you gain type safety and the ability to use it properly with metadata.
Would still love to be able to use the simpler next version of this though, as I'm no longer able to optimize images without significant work.
Facing somewhat similar issue where if I import(...)
a MDX file that has a dynamic import path (based on the page slug) then it will straight throw up an error. Although using a static import path inside dynamic import works.
~Edit: Repoduction - https://codesandbox.io/p/sandbox/goofy-chatterjee-vq74xd~
Next.js 13 being "stable" is an enormous joke. I manage a blog for a large company and part of my work is making sure its using the latest technologies for pretty much everything, and gradually upgrading to Next.js 13 is impossible because it completely botched dynamic importing in generateStaticParams
(in this case, mdx
). Nobody wants to create a page.tsx
file for every blog post. It's as if Next.js didn't account for this possibility and just wanted to release v13 ASAP, maybe because of competitive pressure from other frameworks. Corporate-driven desire to satisfy stakeholders over actual framework usability. Nuts.
I got this working with a dynamic page like follows:
// @/app/blog/[id]/page.tsx
import dynamic from "next/dynamic";
import { convertDate, getAllPostsMeta, getPageData } from "@/lib/posts";
export default async function Post({ params }: { params: { id: string } }) {
const { id } = params;
const { meta } = await getPageData(`${id}.mdx`);
const { title, author, date } = meta;
const convertedDate = convertDate(date);
const Post = dynamic(() => import(`../posts/${id}.mdx`));
if (!Post) return <div>Loading...</div>;
return (
<div key={id}>
<h1>{title}</h1>
<h3>
{author} - {convertedDate}
</h3>
<Post />
</div>
);
}
// generate route segments
export async function generateStaticParams() {
const posts = await getAllPostsMeta();
return posts;
}
// @/lib/posts.ts
import fs from "fs";
import path from "path";
export interface MetaData {
[key: string]: any;
}
const postsDirectory: string = path.join(process.cwd(), "app/blog/posts");
const fileNames: string[] = fs.readdirSync(postsDirectory);
export async function getPageData(id: string): Promise<MetaData> {
const { meta } = require(`@/app/blog/posts/${id}`);
const postData: MetaData = {
meta: { ...meta, id: id.replace(/\.mdx/, "") },
};
return postData;
}
export async function getAllPostsMeta(): Promise<MetaData[]> {
let posts = [];
for (const file of fileNames) {
const { meta } = await getPageData(file);
posts.push(meta);
}
posts.sort((a: MetaData, b: MetaData) => {
return a.date < b.date ? 1 : -1;
});
return posts;
}
export function convertDate(date: string): string {
return new Date(date).toDateString();
}
I only have one page.tsx
for all posts now
And a .mdx file would look like the following:
export const meta = {
title: 'Post title',
author: 'tannerabread',
}
## Introduction
Some content here
@tannerabread
I got this working with a dynamic page like follows:
// @/app/blog/[id]/page.tsx import dynamic from "next/dynamic"; import { convertDate, getAllPostsMeta, getPageData } from "@/lib/posts"; export default async function Post({ params }: { params: { id: string } }) { const { id } = params; const { meta } = await getPageData(`${id}.mdx`); const { title, author, date } = meta; const convertedDate = convertDate(date); const Post = dynamic(() => import(`../posts/${id}.mdx`)); if (!Post) return <div>Loading...</div>; return ( <div key={id}> <h1>{title}</h1> <h3> {author} - {convertedDate} </h3> <Post /> </div> ); } // generate route segments export async function generateStaticParams() { const posts = await getAllPostsMeta(); return posts; }
// @/lib/posts.ts import fs from "fs"; import path from "path"; export interface MetaData { [key: string]: any; } const postsDirectory: string = path.join(process.cwd(), "app/blog/posts"); const fileNames: string[] = fs.readdirSync(postsDirectory); export async function getPageData(id: string): Promise<MetaData> { const { meta } = require(`@/app/blog/posts/${id}`); const postData: MetaData = { meta: { ...meta, id: id.replace(/\.mdx/, "") }, }; return postData; } export async function getAllPostsMeta(): Promise<MetaData[]> { let posts = []; for (const file of fileNames) { const { meta } = await getPageData(file); posts.push(meta); } posts.sort((a: MetaData, b: MetaData) => { return a.date < b.date ? 1 : -1; }); return posts; } export function convertDate(date: string): string { return new Date(date).toDateString(); }
I only have one
page.tsx
for all posts nowAnd a .mdx file would look like the following:
export const meta = { title: 'Post title', author: 'tannerabread', } ## Introduction Some content here
How do you make it work?
Apparently
const Post = dynamic(() => import(`../posts/${id}.mdx`));
Doesn't work. It always returns error code 500
It will just work like this as static file
const Post = dynamic(() => import(`../posts/post-1.mdx`));
Maybe share a full repo?
Verify canary release
Provide environment information
Which area(s) of Next.js are affected? (leave empty if unsure)
MDX (@next/mdx)
Link to the code that reproduces this issue
https://codesandbox.io/p/github/HMilbradt/next-bug/main?file=%2FREADME.md&workspace=%257B%2522activeFileId%2522%253A%2522clfj5a7i80000g3f115xy7jew%2522%252C%2522openFiles%2522%253A%255B%2522%252FREADME.md%2522%255D%252C%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522gitSidebarPanel%2522%253A%2522COMMIT%2522%252C%2522spaces%2522%253A%257B%2522clfj5a9m9001d356ruhenriv3%2522%253A%257B%2522key%2522%253A%2522clfj5a9m9001d356ruhenriv3%2522%252C%2522name%2522%253A%2522Default%2522%252C%2522devtools%2522%253A%255B%257B%2522key%2522%253A%2522clfj5a9ma001e356rnp4xvztx%2522%252C%2522type%2522%253A%2522PROJECT_SETUP%2522%252C%2522isMinimized%2522%253Afalse%257D%255D%257D%257D%252C%2522currentSpace%2522%253A%2522clfj5a9m9001d356ruhenriv3%2522%252C%2522spacesOrder%2522%253A%255B%2522clfj5a9m9001d356ruhenriv3%2522%255D%252C%2522hideCodeEditor%2522%253Afalse%257D
To Reproduce
Simply open the app. It will break immediately.
To show that MDX is actually working, you can also remove the metadata from the "broken" page.tsx, or remove the dynamic import.
Describe the Bug
The build throws saying that you're attempting to use metadata in a client component. I'm assuming that the use of the dynamic import automatically means I'm using a client component.
Expected Behavior
I would expect that this would work out of the box. MDX renders fine when it's imported normally, and while this might be somewhat uncommon to do, it's incredibly useful for building out a blog based on MDX.
Note that this example is an overly simplified version of what I'm doing. In practice, I have a number of content files that I want to co-locate in my app directory, and in order to show my list of this content I need to dynamically load them in.
Thanks!
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
NEXT-1195