Closed laurentsenta closed 1 year ago
The problem is that MDX adds getStaticProps
to layoutProps
, but then next/babel
removes the export, so now the reference is missing. I think you can patch the issue by creating this Babel plugin, which removes all data fetching functions from layoutProps
:
/**
* Currently it's not possible to export data fetching functions from MDX pages
* because MDX includes them in `layoutProps`, and Next.js removes them at some
* point, causing a `ReferenceError`.
*
* https://github.com/mdx-js/mdx/issues/742#issuecomment-612652071
*
* This plugin can be removed once MDX removes `layoutProps`, at least that
* seems to be the current plan.
*/
// https://nextjs.org/docs/basic-features/data-fetching
const DATA_FETCH_FNS = ['getStaticPaths', 'getStaticProps', 'getServerProps']
module.exports = () => {
return {
visitor: {
ObjectProperty(path) {
if (
DATA_FETCH_FNS.includes(path.node.value.name) &&
path.findParent(
(path) =>
path.isVariableDeclarator() &&
path.node.id.name === 'layoutProps',
)
) {
path.remove()
}
},
},
}
}
Then add it to your Babel configuration, let's say we call it babel-plugin-nextjs-mdx-patch.js
:
{
"presets": ["next/babel"],
"plugins": ["./babel-plugin-nextjs-mdx-patch"]
}
Hey!
I think I'm trying to do something like that.
I defined a layout for my mdx files like this:
import PostLayout from "~/components/Layout/Post"
const App = ({ Component, pageProps }) => (
<MDXProvider components={{ wrapper: PostLayout }}>
<Component {...pageProps} />
</MDXProvider>
)
however I need to do something first via getStaticProps
before rendering the mdx via my layout.
I tried to add getStaticProps
into my layout, but it's never called.
So I try a kind of hack to put my layout into a page as apparently only pages
can have getStaticProps
but it didn't change anything.
My question is: by using mdx, how I can intercept it before it's rendered by my component (which is PostLayout
)
example:
export async function getStaticProps() {
console.log("hello")
return {
props: {
test: test,
},
}
}
const PostLayout = ({ children, meta: { title, cover }, test }) => ()
(nothing happened)
You have to export getStaticProps
from the MDX page itself, not layout.
There is one syntax that seems to circumvent this incompatibility between MDX and Next.js:
export { getStaticProps } from 'path/to/module'
# Title
Your MDX content...
But I'd say that it's somewhat of a bug in MDX that this works because all exports are meant to be passed as props to layout, e.g. export const meta = { ... }
is a common pattern, and I'd expect export { meta } from './other-data
to behave the same, but it doesn't.
Oh I see. Thank you for your answer. The thing is I would like to do "lots of stuff" so it could be a bit annoying to put all this logic inside each mdx as my mdx files are especially posts for my blog.
Indeed, I only use export for metadata instead of using frontmatter. But for instance, knowing the path where I am, I don't want to put this logic inside this.
I found a trick by using the router in the layout to get this instead:
const PostLayout = ({ children, meta: { title, cover } }) => {
const { pathname } = useRouter()
const splitUri = pathname.split("/")
const homeUrl = `/${splitUri[1]}/${splitUri[2]}`
const date = dayjs(getDateFromPath(pathname)).format("DD MMMM YYYY")
Maybe I could do a kind of wrapper for metadata with getStaticProps
as you said yes, thanks for this idea!
I wondered if this kind of global state stuff could be done on getInitialProps in a Custom App which puts the data an own Context Provider. Would that work with fully static sites?
The thing is I would like to do "lots of stuff" so it could be a bit annoying to put all this logic inside each mdx as my mdx files are especially posts for my blog.
Definitely annoying! This is why I went a little nuts in my configuration by building custom unified plugins for MDX, for example:
Depending on what you want you might find them useful, they are written to be reusable and the tests should help you figure out how they work. I was planning to publish them, but it seems like MDX v2 is moving towards this direction anyway.
I wondered if this kind of global state stuff could be done on getInitialProps in a Custom App which puts the data an own Context Provider. Would that work with fully static sites?
No, getInitialProps
is run by the server. 😕
Super interesting! Thank you very much @silvenon
Could you expand on what you're trying to accomplish exactly with getStaticProps
? That will help us understand what MDX solutions will work.
For the moment:
I would like to know the path of the current MDX parsed
For what? Unified plugins know the file path, but it depends on what you want to do with this path.
would also like to require the images I put in the MDX cf
I don't think that MDX supports interpolation in Markdown yet, but importing the image should get the src, then add it to an <img />
element:
import testGif from './test.gif`
<img src={testGif} />
I haven't tried it, but is there a downside to this approach?
About the current MDX, for the moment in my layout I do something like:
const PostLayout = ({ children, meta: { title, description, cover } }) => {
const { pathname } = useRouter()
const LANG = getLangFromPathname(pathname)
const splitUri = pathname.split("/")
const homeUrl = `/${splitUri[1]}/${splitUri[2]}`
const date = dayjs(getDateFromPath(pathname))
.locale(LANG)
.format("DD MMMM YYYY")
const fromNow = dayjs(getDateFromPath(pathname)).locale(LANG).fromNow()
and I'm not sure it's the best way, to use the router for this instead of the file path via the compiler/provider.
About the image, it means I can't use remark-images
for instance. It also breaks some editors like markdown preview on VS Code 😬 (which can be fixed for sure).
And I don't like using two lines for one image.
My solution for the moment is <img src={require("./image.png")} />
I tried something like making a component called <Img />
where I require the image there instead in the MDX file, but I don't know the full path there so <Img src="./image.png" />
didn't work.
Maybe you're right, I should create my own remark plugin for that in fact. It's what I already thought about it. :)
This remark plugin could look something like this (adjust the logic accordingly because file.path
is a full path):
const remarkMdxFromNow = () => (tree, file) => {
const LANG = getLangFromPathname(file.path)
const splitUri = file.path.split("/")
const homeUrl = `/${splitUri[1]}/${splitUri[2]}`
const date = dayjs(getDateFromPath(file.path))
.locale(LANG)
.format("DD MMMM YYYY")
const fromNow = dayjs(getDateFromPath(file.path)).locale(LANG).fromNow()
file.data.fromNow = fromNow
}
module.exports = remarkMdxFromNow
Now you saved it to file.data
, and you could build a separate plugin that inserts an export
statement into your MDX file, you can copy my remark-mdx-export-file-data.
Then you would apply these two plugins like this:
const fromNow = require('./etc/remark-mdx-from-now')
const exportFileData = require('./etc/remark-mdx-export-file-data')
// ...
{
remarkPlugins: [
fromNow,
[exportFileData, ['fromNow']],
]
}
Now your MDX file will both export fromNow
and provide it to the layout, if one is provided. Notice that you can use remark-mdx-export-file-data for exporting anything attached to file.data
.
But this is obviously a temporary solution, MDX should provide a much better interface to do stuff like this. I haven't been following closely, but I think that's what MDX v2 will do.
About the image, it means I can't use remark-images for instance. It also breaks some editors like markdown preview on VS Code 😬 (which can be fixed for sure).
I didn't try to solve this problem before, so I don't know if there's a way. 🤷
Thank you so much for this explanation, and for your time. I'll give a try of this!
I have some work I want to do in getStaticProps
for all of my mdx pages (much like @kud, I think).
Providing a custom renderer for @mdx-js/loader
seems like a promising approach.
next.config.js
const mdxRenderer = `
import React from 'react'
import { mdx } from '@mdx-js/react'
export async function getStaticProps () {
return {
props: {
foo: 'bar'
}
}
}
`
const withMdx = require('@next/mdx')({
options: {
renderer: mdxRenderer
}
})
module.exports = withMdx({
pageExtensions: ['tsx', 'mdx'],
})
layout.tsx
import React from 'react'
interface Props {
foo: string
}
const Layout: React.FC<Props> = ({ children, foo }) => (
<div>
<p>The static prop value is: {foo}</p>
{children}
</div>
)
export default Layout
The static props seem to get passed into my layout just like I wanted, but I haven't thoroughly tested yet.
My next thought is whether I can export additional static prop getter functions from mdx pages to merge into the getStaticProps
function defined in the mdx renderer.
I get a similar issue, but with async
, after writing:
export async function getStaticProps(){
return { props: { data: await myData() } }
}
Rewriting to:
export const getStaticProps = async () => {
return { props: { data: await myData() } }
}
Just the same as you:
Spent some time trying to figure this out and can confirm that MDX V2 does fix this issue. If you're ok being on the next
version, you just need to install "@mdx-js/loader": "next"
and set the loader up yourself.
@souporserious Thanks for this! I installed the next
version and I'm able to now export getStaticProps
from my mdx pages, making life much easier. I'm fine with working around any stability issues as this is for a small personal blog anyway.
No problem, glad it could help! In case you need extra markdown features like rendering tables, I ran into this issue switching to latest. You can fix it by adding remark-gfm:
{ loader: '@mdx-js/loader', options: { remarkPlugins: [gfm] }}
I've noticed that with @mdx-js/loader
(latest
or next
) and xdm
, using getStaticProps
in .mdx
files isn't equivalent to a .tsx
file.
For example, suppose I do something like what @leerob does with next-mdx-remote
, but in my .mdx
file:
---
title: My Test Page
---
import { getFiles, getFileBySlug } from '@/lib/mdx';
...Some content goes here...
export async function getStaticProps({ params }) {
const post = await getFileBySlug('blog', params.slug);
const tweets = await getTweets(post.tweetIDs);
return { props: { ...post, tweets } };
}
I'll get this error, presumably because getStaticProps
and it's imports aren't being stripped from the client-side bundle:
../node_modules/fs.realpath/index.js:8:0 Module not found: Can't resolve 'fs' null
For comparison, a vanilla test.tsx
page can use the utilities just fine and Next.js seems to strip getStaticProps
and its dependencies:
import { getFiles, getFileBySlug } from '@/lib/mdx';
import { getContentPaths } from "@/utils/getContentPaths";
export default function Test() {
return <h1>Test</h1>;
}
export async function getStaticProps({ params }) {
const post = await getFileBySlug('blog', params.slug);
const tweets = await getTweets(post.tweetIDs);
return { props: { ...post, tweets } };
}
Please verify that your issue can be recreated with next@canary
.
please verify canary
label?We noticed the provided reproduction was using an older version of Next.js, instead of canary
.
The canary version of Next.js ships daily and includes all features and fixes that have not been released to the stable version yet. You can think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces by running npm install next@canary
and test it in your project, using your reproduction steps.
If the issue does not reproduce with the canary
version, then it has already been fixed and this issue can be closed.
canary
?The safest way is to install next@canary
in your project and test it, but you can also search through closed Next.js issues for duplicates or check the Next.js releases.
canary
now?Next.js does not backport bug fixes to older versions of Next.js. Instead, we are trying to introduce only a minimal amount of breaking changes between major releases.
An issue with the please verify canary
that receives no meaningful activity (e.g. new comments that acknowledge verification against canary
) will be automatically closed and locked after 30 days.
If your issue has not been resolved in that time and it has been closed/locked, please open a new issue, with the required reproduction, using next@canary
.
Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps. Furthermore, you can upvote the issue using the :+1: reaction on the topmost comment (please do not comment "I have the same issue" without repro steps). Then, we can sort issues by votes to prioritize.
We look into every Next.js issue and constantly monitor open issues for new comments.
However, sometimes we might miss one or two due to the popularity/high traffic of the repository. We apologize, and kindly ask you to refrain from tagging core maintainers, as that will usually not result in increased priority.
Upvoting issues to show your interest will help us prioritize and address them as quickly as possible. That said, every issue is important to us, and if an issue gets closed by accident, we encourage you to open a new one linking to the old issue and we will look into it.
This issue has been automatically closed because it wasn't verified against next@canary. If you think it was closed by accident, please leave a comment. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.
This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.
Bug report
I'm trying to use nextjs and mdx to build a simple site. When I export getStaticProps, I get an "undefined" error. It looks like an issue that happens client side.
To Reproduce
I followed the "with-mdx" example to add mdx pages to my application. https://github.com/zeit/next.js/tree/canary/examples/with-mdx
I try to generate static props from the mdx using exports (https://mdxjs.com/getting-started#exports)
I get an undefined error:
The
<pre>{hello: "world"}</pre>
appears on my webpage. It looks like this error is client side only, and the code behave as expect on the server.Screenshot of the full error below.
Expected behavior
I expect to see the props and the content.
Screenshots
System information
Thanks for supporting this project!