Create loader that maps URL path to a component using Webpack in Next JS

156 views Asked by At

I want to create a loader in Next.js which uses Webpack underhood it's next.config.js config file that will load Blog.js for /blog route & Tutorial.js for /tutorial route.

The MDX data is in pages/ directory. pages/ contains blog/ & /tutorial which have their own .mdx files.

My folder structure is as follows:

.
├── README.md
├── components
│   ├── Blog.js
│   ├── Image.js
│   └── Tutorial.js
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│   ├── blog
│   │   ├── hello-world
│   │   │   ├── Rustin_Cohle.jpg
│   │   │   └── index.mdx
│   │   └── shit-world
│   │       └── index.mdx
│   ├── blog.js
│   ├── index.js
│   ├── tutorial
│   │   └── console-log-in-javascript
│   │       └── index.mdx
│   └── tutorial.js
├── prettier.config.js
├── src
└── utils
    ├── blog
    │   ├── getAllBlogPreviews.js
    │   ├── getStaticPaths.js
    │   └── getStaticProps.js
    ├── common
    │   ├── getAllPreviews.js
    │   ├── getStaticFilePaths.js
    │   └── getStaticFileProps.js
    ├── date.js
    ├── mdxUtils.js
    └── tutorial
        ├── getAllTutorialPreviews.js
        ├── getStaticPaths.js
        └── getStaticProps.js

My next.config.js file looks like:

const { createLoader } = require('simple-functional-loader')
const rehypePrism = require('@mapbox/rehype-prism')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
    enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer({
    pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
    experimental: {
        modern: true,
    },
    webpack: (config, options) => {
        config.module.rules.push({
            test: /\.(svg|png|jpe?g|gif|mp4)$/i,
            use: [
                {
                    loader: 'file-loader',
                    options: {
                        publicPath: '/_next',
                        name: 'static/media/[name].[hash].[ext]',
                    },
                },
            ],
        })

        const mdx = [
            options.defaultLoaders.babel,
            {
                loader: '@mdx-js/loader',
                options: {
                    rehypePlugins: [rehypePrism],
                },
            },
        ]

        config.module.rules.push({
            test: /\.mdx$/,
            oneOf: [
                {
                    resourceQuery: /preview/,
                    use: [
                        ...mdx,
                        createLoader(function (src) {
                            console.log('src ')
                            console.log(src)
                            console.log('src ')
                            if (src.includes('<!--more-->')) {
                                const [preview] = src.split('<!--more-->')
                                return this.callback(null, preview)
                            }

                            const [preview] = src.split('<!--/excerpt-->')
                            return this.callback(null, preview.replace('<!--excerpt-->', ''))
                        }),
                    ],
                },
                {
                    test: /blog/,
                    use: [
                        ...mdx,
                        createLoader(function (src) {
                            const content = [
                                'import Blog from "@/components/Blog"',
                                'export { getStaticProps } from "@/utils/blog/getStaticProps"',
                                'export { getStaticPaths } from "@/utils/blog/getStaticPaths"',
                                'console.log("/blog")',
                                src,
                                'export default (props) => <Blog meta={meta} {...props} />',
                            ].join('\n')

                            if (content.includes('<!--more-->')) {
                                return this.callback(null, content.split('<!--more-->').join('\n'))
                            }

                            return this.callback(null, content.replace(/<!--excerpt-->.*<!--\/excerpt-->/s, ''))
                        }),
                    ],
                },
                {
                    test: /tutorial/,
                    use: [
                        ...mdx,
                        createLoader(function (src) {
                            const content = [
                                'import Tutorial from "@/components/Tutorial"',
                                'export { getStaticProps } from "@/utils/tutorial/getStaticProps"',
                                'export { getStaticPaths } from "@/utils/tutorial/getStaticPaths"',
                                'console.log("/tutorial")',
                                src,
                                'export default (props) => <Tutorial meta={meta} {...props} />',
                            ].join('\n')

                            if (content.includes('<!--more-->')) {
                                return this.callback(null, content.split('<!--more-->').join('\n'))
                            }

                            return this.callback(null, content.replace(/<!--excerpt-->.*<!--\/excerpt-->/s, ''))
                        }),
                    ],
                },
            ],
        })

        return config
    },
})

I am using test: /blog/ & test: /tutorial/ but it doesn't work that way. I am not sure how I can make it work?

I have the uploaded my complete code on the tailwind branch at Github → https://github.com/deadcoder0904/blog-mdx-next/tree/tailwind which is adapted from https://github.com/tailwindlabs/blog.tailwindcss.com

How do I make it that blog/ uses Blog.js & tutorial/ uses Tutorial.js?

1

There are 1 answers

0
deadcoder0904 On BEST ANSWER

I had to use Webpack's include feature like:

include: [path.resolve(__dirname, 'pages/blog')]

instead of

test: /blog/

& same with tutorial.

Here's the commit → https://github.com/deadcoder0904/blog-mdx-next/commit/84ad0042de3ec229dd130ef2491511c5e3090110