gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.28k stars 10.31k forks source link

MDX content only rendered after refresh #38849

Open datawookie opened 9 months ago

datawookie commented 9 months ago

Preliminary Checks

Description

I'm considering replacing AsciiDoc with MDX in one of my production Gatsby sites. But before I commit I wanted to just figure out how it would work.

I have a small test site which works with gatsby-plugin-mdx version 3.20.0. It's using <MDXRenderer> to render the MDX. The repository for the test site is here, Make sure that you are on the 28-mdx branch.

However, I'd like to upgrade to a more recent version of the plugin. Having read the documentation I see that there is a major change at version 4.0.0. I applied the migration steps specified in the documentation. The resulting code can be found here. Make sure that you are on the 29-mdx-updated branch.

In gatsby-node.js I have the following:

  const indexPage = require.resolve("./src/pages/index.js");

  createPage({
    path: '/',
    component: `${indexPage}?__contentFilePath=/site/content/index.mdx`,
  });

The site builds fine. But when I first visit the landing page the content from the MDX file (which should have been contained in children) is absent.

Screenshot 2024-02-02 at 17-01-43 WhimsyWeb   MDX

However, if I make a minor edit to src/pages/index.js (which renders the landing page) then the site refreshes and the MDX content is suddenly visible.

Screenshot 2024-02-02 at 17-02-01 WhimsyWeb   MDX

I have fiddled around with a bunch of things, but regardless of what I do the MDX content is not present until the site refreshes.

I have tried updating to the most recent version of the plugin with the same outcome.

The refresh behaviour is only apparent with the development server. If I make a production build then the MDX content is simply not there.

Reproduction Link

https://gitlab.com/datawookie/gatsby-whimsyweb/-/tree/29-mdx-updated?ref_type=heads

Steps to Reproduce

  1. Clone the [repository]().
  2. Checkout the 29-mdx-updated branch.
  3. Start the development server.

Expected Result

The landing page should include text from the MDX file: "MDX is a markup language that combines Markdown and JSX...".

Actual Result

This text is not present until I refresh the site (by simply editing a file so that the development server rebuilds).

Environment

System:
    OS: Linux 6.2 Debian GNU/Linux 12 (bookworm) 12 (bookworm)
    CPU: (16) x64 12th Gen Intel(R) Core(TM) i7-1270P
    Shell: 5.2.15 - /bin/bash
  Binaries:
    Node: 21.5.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.4 - /usr/local/bin/npm
  npmPackages:
    gatsby: 4.25.8 => 4.25.8
    gatsby-plugin-mdx: 4.0.0 => 4.0.0
    gatsby-plugin-netlify: 5.1.1 => 5.1.1
    gatsby-source-filesystem: 4.25.0 => 4.25.0
  npmGlobalPackages:
    gatsby-cli: 5.13.1

Config Flags

No response

olehpratsko commented 9 months ago

@datawookie you are cooking gatsby in a wrong way. First, if you want to use component as a template for rendering page with createPage, use templates folder instead of pages here: const indexPage = require.resolve("./src/pages/index.js"); wrong const pageTemplate = require.resolve("./src/templates/page.js");

pages folder is the default reserved folder for gatsby to render static pages, so your really braking gatsby functionality with your pattern.

Second thing, you should ask yourself why do you query allMdx on the line 6, if you never use result of the query. You'll be surprised, but if you remove the query, you will get exactly the same result. Use the data returned from query to render pages like that:

const result = await graphql(`
    {
      mdxPages: allMdx {
        nodes {
          id
          frontmatter {
            slug
          }
          internal {
            contentFilePath
          }
        }
      }
    }
  `);

  if (result.errors) {
    console.log('MDX query failed for some reason')
    return;
  }

  const mdxPages = result.data.mdxPages.nodes;

  mdxPages.forEach(node => {
      createPage({
        path: node.frontmatter.slug, // if your frontmatter contains slug field
        component: `${pageTemplate}?__contentFilePath=${node.internal.contentFilePath}`, // yes, contentFilePath should be dynamic
        context: {
          id: node.id,
        },
      });
  });
datawookie commented 9 months ago

Thank you, @olehpratsko. You're quite right: I was really mangling that! Your suggestion fixed my issue.

mustaquenadim commented 4 months ago

Hello @olehpratsko,

I was using simple markdown (gatsby-transformer-remark) for writing content. But now I want to use advanced markdown (gatsby-plugin-mdx). So I decided to migrate my gatsby app from version 3 to version 5. I successfully migrated the gatsby app. But when trying to migrate remark to mdx, I'm facing critical issues like fileAbsolutePath word doesn't exist, not getting body content of the .md file with children props and not sure what to use for styling markdown content after removing dangerouslySetInnerHTML.

Can you tell me why am I getting the following errors?

Invalid left-hand side in prefix operation. (1:2)

> 1 | ---
    |   ^
  2 | title: Understanding Inline, Inline-Block, and Block Elements in HTML
  3 | description: Unlock the secrets of inline, inline-block, and block elements in HTML
  4 | date: 2023-07-02
 ERROR #gatsby-plugin-mdx_10001  PLUGIN

Failed to compile the file "D:/PROJECTS/mustaquenadim.github.io/content/posts/inline-inline-block-and-block-elements-in-html/index.md". Original error message:

Unexpected character `/` (U+002F) before local name, expected a character that can start a name, such as a letter, `$`, or `_` (note: to create a link in MDX, use `[text](url)`)

Here is my folder structure image

gatsby-node.js

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */

const path = require('path');
const _ = require('lodash');

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions;
  const postTemplate = path.resolve(`src/templates/post.js`);
  const tagTemplate = path.resolve('src/templates/tag.js');

  const result = await graphql(`
    {
      postsRemark: allMdx(
        filter: { internal: { contentFilePath: { regex: "/content/posts/" } } }
        sort: { frontmatter: { date: DESC } }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              slug
            }
            internal {
              contentFilePath
            }
          }
        }
      }
      tagsGroup: allMdx(limit: 2000) {
        group(field: { frontmatter: { tags: SELECT } }) {
          fieldValue
        }
      }
    }
  `);

  // Handle errors
  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`);
    return;
  }

  // Create post detail pages
  const posts = result.data.postsRemark.edges;

  posts.forEach(({ node }) => {
    createPage({
      path: node.frontmatter.slug,
      component: `${postTemplate}?__contentFilePath=${node.internal.contentFilePath}`,
      context: {
        // additional data can be passed via context
        slug: node.frontmatter.slug,
      },
    });
  });

  // Extract tag data from query
  const tags = result.data.tagsGroup.group;
  // Make tag pages
  tags.forEach(tag => {
    createPage({
      path: `/pensieve/tags/${_.kebabCase(tag.fieldValue)}/`,
      component: tagTemplate,
      context: {
        tag: tag.fieldValue,
      },
    });
  });
};

// https://www.gatsbyjs.org/docs/node-apis/#onCreateWebpackConfig
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  // https://www.gatsbyjs.org/docs/debugging-html-builds/#fixing-third-party-modules
  if (stage === 'build-html' || stage === 'develop-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /scrollreveal/,
            use: loaders.null(),
          },
          {
            test: /animejs/,
            use: loaders.null(),
          },
          {
            test: /miniraf/,
            use: loaders.null(),
          },
        ],
      },
    });
  }

  actions.setWebpackConfig({
    resolve: {
      alias: {
        '@components': path.resolve(__dirname, 'src/components'),
        '@config': path.resolve(__dirname, 'src/config'),
        '@fonts': path.resolve(__dirname, 'src/fonts'),
        '@hooks': path.resolve(__dirname, 'src/hooks'),
        '@images': path.resolve(__dirname, 'src/images'),
        '@pages': path.resolve(__dirname, 'src/pages'),
        '@styles': path.resolve(__dirname, 'src/styles'),
        '@utils': path.resolve(__dirname, 'src/utils'),
      },
    },
  });
};

/src/templates/post.js

import { Layout } from '@components';
import { MDXProvider } from '@mdx-js/react';
import { graphql, Link } from 'gatsby';
import kebabCase from 'lodash/kebabCase';
import PropTypes from 'prop-types';
import React from 'react';
import { Helmet } from 'react-helmet';
import styled from 'styled-components';

const StyledPostContainer = styled.main`
  max-width: 1000px;
`;
const StyledPostHeader = styled.header`
  margin-bottom: 50px;
  .tag {
    margin-right: 10px;
  }
`;
const StyledPostContent = styled.div`
  margin-bottom: 100px;
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    margin: 2em 0 1em;
  }

  p {
    margin: 1em 0;
    line-height: 1.5;
    color: var(--light-slate);
  }

  a {
    ${({ theme }) => theme.mixins.inlineLink};
  }

  code {
    background-color: var(--lightest-navy);
    color: var(--lightest-slate);
    border-radius: var(--border-radius);
    font-size: var(--fz-sm);
    padding: 0.2em 0.4em;
  }

  pre code {
    background-color: transparent;
    padding: 0;
  }
`;

const PostTemplate = ({ data, location, children }) => {
  console.log('My single post data____________', data, children);
  if (!data) {
    return (
      <Layout location={location}>
        <StyledPostContainer>
          <p>No Post Found</p>
        </StyledPostContainer>
      </Layout>
    );
  }

  const { frontmatter } = data.mdx;
  const { title, date, tags } = frontmatter;

  return (
    <Layout location={location}>
      <Helmet title={title} />

      <StyledPostContainer>
        <span className="breadcrumb">
          <span className="arrow">&larr;</span>
          <Link to="/pensieve">All memories</Link>
        </span>

        <StyledPostHeader>
          <h1 className="medium-heading">{title}</h1>
          <p className="subtitle">
            <time>
              {new Date(date).toLocaleDateString('en-US', {
                year: 'numeric',
                month: 'long',
                day: 'numeric',
              })}
            </time>
            <span>&nbsp;&mdash;&nbsp;</span>
            {tags &&
              tags.length > 0 &&
              tags.map((tag, i) => (
                <Link key={i} to={`/pensieve/tags/${kebabCase(tag)}/`} className="tag">
                  #{tag}
                </Link>
              ))}
          </p>
        </StyledPostHeader>

        {/* <StyledPostContent>{children}</StyledPostContent> */}
        <MDXProvider>{children}</MDXProvider>
      </StyledPostContainer>
    </Layout>
  );
};

export default PostTemplate;

PostTemplate.propTypes = {
  data: PropTypes.object,
  location: PropTypes.object,
};

export const pageQuery = graphql`
  query ($slug: String!) {
    mdx(frontmatter: { slug: { eq: $slug } }) {
      frontmatter {
        title
        description
        date
        slug
        tags
      }
    }
  }
`;