Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Building MDX Pages via getStaticProps #14718

Closed
joe-bell opened this issue Jun 30, 2020 · 12 comments
Closed

Feature Request: Building MDX Pages via getStaticProps #14718

joe-bell opened this issue Jun 30, 2020 · 12 comments

Comments

@joe-bell
Copy link
Contributor

joe-bell commented Jun 30, 2020

Feature request

Given that Next.js is investing efforts into static generation with the likes of getStaticProps and getStaticPaths, it would be great to leverage the full power of MDX without being tied to the Webpack loader's (@next/mdx) current constraints.

The current use of @next/mdx has its limitations:

  • Constrained to a single layout
  • No ability to transform MDX to HTML for content feeds (e.g. RSS)
  • No ability to parse frontMatter
  • No ability to fetch from remote sources (e.g. CMS)
  • Difficult to transform raw content

next-mdx-enhanced does a great job of alleviating some of these concerns but still relies heavily on Webpack, rather than utilizing Next's static functions.

This PR suggests @next/mdx could be extended to offer users more flexibility (but hopefully still easy-to-follow) on how their pages are built. This suggestion aims to provide:

Describe the solution you'd like

Note: These examples would still rely on the consumer configuring their own MDXProvider (e.g. Theme-UI)

  1. An MDXRenderer that allows for full control of layout:

    (Importing an mdx file would return an object containing frontMatter and mdxContent)

    // e.g. src/pages/blog/[id].tsx
    import * as React from "react";
    import { GetStaticProps, GetStaticPaths, NextPage } from "next";
    import Error from "next/error";
    import LayoutPost from "../layouts/post";
    import { somePrivateFunctionToGlobMdxFileNames } from "./utils";
    // but maybe this could be a function added to `@next/mdx`
    
    // Proposed:
    import { MDXRenderer } from "@next/mdx";
    
    type BlogPostProps = { error?: boolean };
    
    export const getStaticPaths: GetStaticPaths = async () => ({
      paths: somePrivateFunctionToGlobMdxFileNames("../posts"),
    });
    
    export const getStaticProps: GetStaticProps<BlogPostProps> = async (
      context
    ) => {
      // this could also be a remote data source
      const res = await import(`../posts/${context.params.id}.mdx`);
      // const { frontMatter, mdxContent } = res
    
      return { props: res || { error: true } };
    };
    
    const BlogPost: React.FC<NextPage & BlogPostProps> = ({ error, ...props }) =>
      error ? (
        <Error statusCode={404} />
      ) : (
        <LayoutPost title={props.frontMatter.title}>
          <MDXRenderer {...props} />
        </LayoutPost>
      );
    
    export default BlogPostPage;
  2. An mdxToStaticMarkup function that allows the MDXRenderer to be converted to static HTML (for RSS feeds etc.).

    mdxToStaticMarkup(<MDXRenderer />);

    With this approach the consumer would have the freedom to transform content if necessary.

Describe alternatives you've considered

One approach I tried back in january was to use MDX's async API in the getStaticProps method directly, but I hit a snag with this issue: mdx-js/mdx#382 (comment)

More importantly though, given Next.js does such a great job of reducing set-up complexity, it would be great to have something that makes MDX more flexible but still easy to use.

Additional context

As of now my availability is open and I would be more than happy to collaborate with the Next.js core team into spiking this as an option (if desired).

@timneutkens
Copy link
Member

timneutkens commented Jun 30, 2020

Just keep in mind that what you're proposing would only allow for fully static html to be output, no client-side hydration would happen based on what you're proposing, meaning no dynamic components (no setState / useEffect etc)

@joe-bell
Copy link
Contributor Author

Sorry @timneutkens I'm not sure I follow; do you mean both functions I've suggested or a specific one in particular? I'm suggesting providing both.

With the MDXRenderer I'm thinking an implementation of MDX's Async API

but the mdxToStaticMarkup is an optional method designed specifically for things like RSS feeds, API routes etc (where we of course wouldn't want client-side hydration)

@timneutkens
Copy link
Member

With the MDXRenderer I'm thinking an implementation of MDX's Async API…

This ships a JS parser to the browser to make it work (increasing bundle sizes tremendously)

You'll likely want to use something like https://github.com/hashicorp/next-mdx-remote

@joe-bell
Copy link
Contributor Author

Gotcha, thanks for the explanation

Do the Next.js core team plan on integrating something similar to next-mdx-remote in the future?

I do feel that something like this is more inline to what Next.js is achieving in other areas, it would be great to see this for MDX too

@IanMitchell
Copy link
Contributor

Jumping in to say I think improvements similar to the ones proposed would be hugely welcome, and I'd be happy to help out! I've been working on redoing my personal website with MDX and Next.js, porting it from Gatsby. It would be cool to see MDX as a seamless option in Next.js SSG. There's been a couple of us working on these issues already too - I don't think it would take a lot of work to get some implemented and shipped.

I know previously it was decided frontmatter is outside the purview of Next.js but I think it helps reduce the barrier of entry significantly, especially for non-technical folks.

Hydrating MDX is a bit of a different beast (I'm running into the problems Tim mentioned), but would love to see if it's possible to create a system that makes it as seamless as it is in Gatsby.

@daneden
Copy link
Contributor

daneden commented Aug 29, 2020

@joe-bell I just added a with-mdx-remote example which might give you what you’re looking for: #16613

I use a more advanced configuration for my own site, which has nested directories and conditionally loaded components. This has been working well for me and feels more native-to-Next.js (versus adding boilerplate MDX-specific code!)

@joe-bell
Copy link
Contributor Author

@daneden thanks for this, it's a huge help!

@joe-bell
Copy link
Contributor Author

I'll go ahead and close this issue seeing as next-mdx-remote does a good enough job of solving this challenge

@deadcoder0904
Copy link

Would love to see this implemented as well.

I tried using next-mdx-remote & next-mdx-enhanced but ran into its limitations (specifically having images in the same folder as the posts that it cannot solve)

I had to use hacks to solve MDX to HTML (for RSS) → #17931

@kripod
Copy link
Contributor

kripod commented Nov 28, 2020

I came across this issue while trying to find a solution for hot reloading .md files without a webpack loader. Using a combination of getStaticPaths and getStaticProps, I'm not quite sure how Fast Refresh could work when a .md file changes.

Would preview mode be sufficient for this use-case? If so, how could it be set up to watch for file changes?

(I think this is relevant because it's a problem to be solved when using getStaticProps instead of a webpack plugin.)

@lunelson
Copy link

lunelson commented May 15, 2021

@kripod although this is a bit OT and this thread is old: the solution I've found for refreshing data from getStaticProps/getStaticPaths is to use router.replace(), which causes those functions to re-run. NOTE however, that it is not a "fast refresh" thing at all, because those functions have to go through their complete fetching and parsing

// at the top of the file
import { useRouter } from 'next/router';

// in your page component
const router = useRouter();

// somewhere in response to an update event - page's props should just update in place
router.replace(router.asPath, router.asPath, { shallow: false, scroll: false });

...the tricky part is how do you get that event: I've built a hook to get pushed updates from a remote CMS, but I'm not sure how it could be done for local files. It would be cool if Next provided a way to add watchers/listeners for this use-case, or maybe add on to the watcher(s) already run by webpack-dev-server

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants