Jacob Maizel
ai generated header image abstract
Portfolio WebsiteA personal website built with NextJS and TailwindCSS
Blog In Progress
Active Development
Open Source
Production
NextJS
TailwindCSS
TypeScript
Vercel

In progress!


About

For my personal site, my main focus was getting something up and running as soon as possible, which had a great dev experience, and was easy to maintain.

Tech Stack


Below are some code snippets from the repo that I found interesting.

MDX Configuration

NextJS has excellent support for MDX, and it was really easy to get up and running. This is where the magic happens. Check the docs here.


Below is the entire config for setting up NextJS to:


next.config.mjs

import createMDX from '@next/mdx';
import toc from '@atomictech/rehype-toc';

import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeSlug from 'rehype-slug';
import rehypeHighlight from 'rehype-highlight';
import langPython from 'highlight.js/lib/languages/python';
import langSwift from 'highlight.js/lib/languages/swift';
import langGo from 'highlight.js/lib/languages/go';
import langRust from 'highlight.js/lib/languages/rust';

/** @type {import('next').NextConfig} */

const nextConfig = {
  pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
};

const withMDX = createMDX({
  options: {
    remarkPlugins: [],
    rehypePlugins: [
      [
        rehypeHighlight,
        {
          languages: {
            python: langPython,
            swift: langSwift,
            go: langGo,
            typescript: langTypescript,
          },
        },
      ],
      [rehypeSlug],
      [rehypeAutolinkHeadings, { behavior: 'wrap' }],
      [toc, { headings: ['h2', 'h3'], placeholder: 'TOCHERE' }],
    ],
  },
}); 

Structuring an MDX Blog Post

There may be a more efficient or dynamic way to do this, but to start I am simply making a new folder which corresponds to the endpoint name, and adding a page.mdx file.


ie. src/app/projects/< name >/page.mdx


At some point in the future I might consider hosting the mdx files on a CDN and dynamically fetching them.


Blog Header

The first step is adding in the blog header component.


import { BlogHeader } from 'src/components/blog/blog-header.tsx'
import { Divider } from 'src/components/ui/divider.tsx'
import { personalWebsiteProject } from 'src/common/projects.ts'
import 'src/styles/highlight-js/github-dimmed.css'

<BlogHeader project={personalWebsiteProject}  />

Above, we are importing our Blog header component, and passing in the project object which contains the title, description, and other metadata.\

The blog header found at src/components/blog/blog-header.tsx Takes in the project object and handles rendering the blog header image, tags, status indicators and github link if there is one.


Table of Contents

You might be wondering what the "TOCHERE" means when looking through any of the blog mdx files. This is a placeholder that tells the rehype-toc plugin where to insert the table of contents. Like I said, rehype plugins + MDX are magic.


And thats really the most crucial part of making an MDX file special here. The ability to import objects and components. After that, you can just write markdown as you normally would.

TOCHERE  

<Divider padding={'py-4'} width={'w-full'} />

**_In progress!_**

<br/>

## About

........

Styling MDX Components

Another fantastic part of the integration between next js and MDX is the ability to override the styling of specific html tags that are automatically generated from the markdown. All of the overrides are done in ./mdx-components.tsx. A piece of that code is shown below.


export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    h1: props => (
      <h1 className=" text-4xl font-extrabold my-6" {...props}>
        {props.children}
      </h1>
    ),
    h2: props => (
      <h2 className=" text-3xl font-bold my-4" {...props}>
        {props.children}
      </h2>
    ),
    h3: props => (
      <h3 className=" text-xl font-semibold my-2" {...props}>
        {props.children}
      </h3>
    ),
    h4: props => (
      <h4 className=" text-lg font-semibold my-2" {...props}>
        {props.children}
      </h4>
    ),
    p: props => (
      <p className="text-base leading-snug" {...props}>
        {props.children}
      </p>
    ),
    img: (props: any) => <Image className=" w-full h-full" {...props} />,
    a: (props: any) => (
      <a className=" text-blue-500" {...props}>
        {props.children}
      </a>
    )
  }}

As you can see, its very convienient to override the styling of any html tag you want here, even being able to use tailwind. Some things will still need !important overrides in globals.css but for the most part this is all you need.


example of a style override needed for specific parent/child combinations. This one is used to properly color the anchor links for each section.

h1 > a, h2 > a, h3 > a, h4 > a {
    color: rgb(100 116 139 / var(--tw-text-opacity)) !important;
}

Code Block Styling

Another crucial part of any technical blog is code block syntax highlighting. Thankfully, this is another integration that feels like magic onces its working.


To accomplish this we will use highlight-js, and the rehype-highlight plugin.


After adding it to the mdx config object in next.config.mjs like so:


      [
        rehypeHighlight,
        {
          languages: {
            python: langPython,
            swift: langSwift,
            go: langGo,
            typescript: langTypescript,
            rust: langRust,
            css: langCss,
          },
        },
      ]

All you need to do is pick a theme from their repo, download it, and import it directly into your mdx file.


import 'src/styles/highlight-js/github-dimmed.css'

You are free to make adjustments to the theme's css as you need.


For example, I fine tuned the code block styling to remove unneccessary scroll bars, and played with the padding and margins.


/* ....... */
pre:has(.hljs), .hljs {
    color: #adbac7;
    background: #22272e;
    border-radius: 1rem;
    display: inline-block;
    overflow-x: scroll;
    margin: 1rem;
    font-size: 0.875rem;
    line-height: 1.25rem;
    scrollbar-width: none;
}

::-webkit-scrollbar {
    width: 0;  
    background: transparent;  
    display: none;
}

code {
    white-space: pre;
    padding: 1rem; 
    margin: 1rem 0.5rem 1rem 1rem;
}
/* CSS Continues... */

The expected behavior from a header is the ability to click on it and be taken to that section of the page. This is accomplished by adding an anchor link to each header.


A combination of the rehype-slug and rehype-autolink-headings plugins will automatically add anchor links to each header with the id's being the same as the header text.

ie. If you were right click on the above header and inspect it, you would find html that looks like this.

<h3 class="text-xl font-semibold my-2" id="adding-anchor-links-to-headers"><a class=" text-blue-500" href="#adding-anchor-links-to-headers">Adding Anchor Links to Headers</a></h3>

Then take a look at the table of contents link that was generated, and you will see the corresponding anchor link.

<a class="toc-link toc-link-h3" href="#adding-anchor-links-to-headers">Adding Anchor Links to Headers</a>

Conclusion

And that about wraps up my journey so far with NextJS and MDX. I was pleasantly surprised with how easy it was to get up and running, and the ease of customization.


Back to Top