Create personal blog with MDX

Install

npm install @next/mdx @mdx-js/loader @mdx-js/react --save-dev

npm install remark-frontmatter remark-mdx-frontmatter --save-dev
npm install to-vfile vfile-matter --save-dev

npm install remark-sectionize  --save-dev
npm install remark-prism
    # CodeBlock.jsx
    npm install prism-react-renderer --save-dev

next.config.mjs

Beside adding multiple plugins for MDX, this file also sets MDXContent.isMDXComponent = true; so that you can check if the component is an MDX component or not using Component.isMDXComponent in your _app.jsx file.

// next.config.mjs
import createMDX from '@next/mdx';

import remarkFrontmatter from 'remark-frontmatter';
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
import remarkSectionize from 'remark-sectionize';
import remarkPrism from 'remark-prism';


// *** START: Export a DEFAULT object, i.e nextConfig
const nextConfig = {}

    // Produce standalone output (For docker)
    // -----------------------------------------------------------------------------
    //  "next start" doesn't generate standalone output.
    //  Generate standalone output if BUILD_STANDALONE=true.
    nextConfig.output = process.env.BUILD_STANDALONE === "true" ? "standalone" : undefined;

    // Produce standalone output (For docker)
    // -----------------------------------------------------------------------------
    //  Don't generate map files in production. Generate only if ENABLE_SOURCE_MAP=true.
    nextConfig.productionBrowserSourceMaps =process.env.ENABLE_SOURCE_MAP === "true" ? true : undefined;


    // Configure pageExtensions to also include *.mdx
    // -----------------------------------------------------------------------------
    nextConfig.pageExtensions = ['js', 'jsx', 'mdx', 'ts', 'tsx']; 


const withMDX = createMDX({
    extension: /\.mdx?$/,
    options: {
        // If you use remark-gfm, you'll need to use next.config.mjs
        // as the package is ESM only
        // https://github.com/remarkjs/remark-gfm#install
        remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter, remarkSectionize, remarkPrism],
        // If you use `MDXProvider`, uncomment the following line.
        // providerImportSource: "@mdx-js/react",

        recmaPlugins: [() => function transformer(ast) {
            // add property isMDXComponent to MDXContent
            ast.body.unshift({
                type: 'ExpressionStatement',
                expression: {
                    type: 'AssignmentExpression',
                    operator: '=',
                    left: {
                        type: 'MemberExpression',
                        object: { type: 'Identifier', name: 'MDXContent' },
                        property: { type: 'Identifier', name: 'isMDXComponent' },
                        computed: false,
                        optional: false
                    },
                    right: { type: 'Literal', value: true }
                }
            })
        }]

        
    },
})


// *** END: Export
// Merge MDX config with Next.js config
export default withMDX(nextConfig)

Import CSS files in _app.jsx for MDX files

  • Copy ./public/postx/ folder too.

    //_app.jsx
    
    import { Header } from "@/landing51/Header";
    import { Footer } from "@/landing51/Footer";
    
    export default function MyApp({ Component, pageProps, router }) {
        /**
         * Show navigation bar everywhere except uri listed in excludeUris.
         * @param {*} pathname 
         * @param {*} excludeUris e.g. ["/ythumb", "/ssd"];
         * @returns 
         */
        function showNavigationBar(pathname, excludeUris) {
            for(const uri of excludeUris) {
                if(pathname.includes(uri))
                    return false;
            }
            return true;
        }
        const excludeUris = [
                                "/templates/d/",
                            ];
    
    
        return (
            <>
                {  showNavigationBar(router.pathname, excludeUris) && <Header /> }
                {
                    Component.isMDXComponent
                        ?
                        <>
                            <link rel="stylesheet" href="/postx/_css/markdown7.css" />
                            <link rel="stylesheet" href="/postx/_css/my-markdown.css" />
                            <link rel="stylesheet" href="/postx/_css/remark-sectionize.css" />
                            <Component {...pageProps} />
                        </>
                        :
                        <Component {...pageProps} />
                }
                {  showNavigationBar(router.pathname, excludeUris) && <Footer /> }
            </>
    
        );
    }
    
    

Import prism CSS style in _document.jsx

//_document.jsx
<link rel="stylesheet" href="https://unpkg.com/prism-themes@1.6.0/themes/prism-coy-without-shadows.css"></link>

Copy custom react components

  • nextjs/components/postx

    # Needed for CodeBlock.jsx
    npm install --save prism-react-renderer
    npm install --save raw-loader
    

Copy ./postx to ./pages/

Add "./components/*, so that you can import components like import { CodeBlock } from "@/code/CodeBlock.jsx"; in your MDX files.

// jsconfig.json    
{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/*": [
                "./src/*",
                "./components/*"
            ]
        }
    }
}

TODOs

  • Title not working when building from standalone.
  • https://github.com/vercel/next.js/issues/14984#issuecomment-730709331