Build an Enhanced Blog Site

Hi there. I guess this might be the first post of this site(except some archives picked from my former blogs). It took a few days to build this site and I can't wait to share the joy I had walking through it.

cover
Trace of Existence2016

toc

Why Next.js and MDX

The goal is to build a responsive blog site looks nice on laptop/tablet/mobile phone. If you ask me why, it's for fun. Though most blog sites are being unnecessary complicated, there's no better chance to research and practice semantic HTML and typography than building a blog site from scratch.

First of all, this site is a part of my progressive personal site matrix along with www.somarl.com and moment.somarl.com, it makes sense to use a consistent framework.

And, is GREAT! It covers most of things we had to concern about from coding to deployment.

Then, for blog posts, is SPECTACULAR! You see, we get an svg in a paragraph, with [<MDXIcon />](https://mdxjs.com) in the source of this post. I considered between markdown and LaTex, but the former lacks ability to insert customized components as the later lacks simple but extensible building tools for Web sites and hard to export to other platforms. I even thoughted about manually writing HTMLs. MDX wiped all my concerns.

What I did to build this site

To work with Next.js, I chose next-mdx-remote as the adapter for MDX posts and build the basic workspace structure under their instructions.

blog
├── posts
│   └── hello.mdx
├── public
│   └── images
│       └── hello
│           └── cover.jpg
└── src
    ├── components
    ├── lib
    ├── styles
    └── pages
        └── post
            └── [slug].tsx
blog
├── posts
│   └── hello.mdx
├── public
│   └── images
│       └── hello
│           └── cover.jpg
└── src
    ├── components
    ├── lib
    ├── styles
    └── pages
        └── post
            └── [slug].tsx

In src/pages/post/[slug].tsx we build the mdx files in posts like

import {postFileSlugsSync, serializePost} from 'src/lib'

export default function PostPage ({compiledSource, scope}) {
    return (
        <article>
            <MDXRemote compiledSource={compiledSource} scope={scope} />
        </article>
    )
}

export const getStaticProps = async ({params: {slug}}) => {
    const {compiledSource, scope} = await serializePost(slug)

    return {
        props: {
            compiledSource,
            scope,
        },
    }
}

export const getStaticPaths = async ctx => {
    return {
        paths: postFileSlugsSync.map(slug => ({
            params: {
                slug,
            },
        })),
        fallback: false,
    }
}
import {postFileSlugsSync, serializePost} from 'src/lib'

export default function PostPage ({compiledSource, scope}) {
    return (
        <article>
            <MDXRemote compiledSource={compiledSource} scope={scope} />
        </article>
    )
}

export const getStaticProps = async ({params: {slug}}) => {
    const {compiledSource, scope} = await serializePost(slug)

    return {
        props: {
            compiledSource,
            scope,
        },
    }
}

export const getStaticPaths = async ctx => {
    return {
        paths: postFileSlugsSync.map(slug => ({
            params: {
                slug,
            },
        })),
        fallback: false,
    }
}

It makes everthing work. We enhance it from here. First of all, style the post rendered.

Let's first check some details of the basic components supported in markdown. How do they look like and what I did with them.

Headings

I added the heading anchor like Github with the help of remark-slug and remark-autolink-headings.

Try hover on the headings if you are viewing this post by a device with a pointer on screen and the anchors will show up.

h2

h3

h4

h5
h6

And let's just try and see how does a very loooooooooooooooooooooooooooooooong heading looks like

Inline texts

There are emphasis, strong and strikethrough.

And we have ABBR.

Thematic break

Lorem Ipsum is simply dummy text of the printing and typesetting industry.


Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

Code and Syntax Highlighting

Both code block and inline code use the background color as Github markdown code blocks.

The issue was about how to implement syntax highlighting. There are choices like highlightjs and prism but I chose a syntax highlighter called "shiki".

The reason is simple, it does syntax parsing and apply TextMate grammar like text editors(VS Code, for example), that's what I call "highlighting". A simple thing explains what does it mean - it highlights mdx files without any extra configurations.

The only thing I needed to do is to find a proper remark plugin, it turned out to be @stefanprobst/remark-shiki with a little patches.

Code block

// Sample javascript code
var s = "JavaScript syntax highlighting";
alert(s);
// Sample javascript code
var s = "JavaScript syntax highlighting";
alert(s);
# Sample python code
s = "Python syntax highlighting"
print(s)
# Sample python code
s = "Python syntax highlighting"
print(s)

We will see much more code blocks in this post, let's leave it here.

Inline code

Inline code looks like this.

List

Simply style them and then

  • Lorem Ipsum is simply dummy text of the printing and typesetting industry.
    • Lorem Ipsum is simply dummy text of the printing and typesetting industry.
      • Lorem Ipsum is simply dummy text of the printing and typesetting industry.
  • Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
  • Lorem Ipsum is simply dummy text of the printing and typesetting industry.
  1. Lorem Ipsum is simply dummy text of the printing and typesetting industry.
  2. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
  3. Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Task lists included.

  • nested unordered lists
  • ordered lists
  • task lists
  • nested task lists

Some thing worth mentioning is that MDX render task list item into

<li><input type="checkbox" checked disabled /> task lists</li>
<li><input type="checkbox" checked disabled /> task lists</li>

The problem is disabled, I don't like its grey accent color in most browsers ;(

So I overrided the customized component like, though readOnly does not make any sense here :)

export default function PostLi ({children}: IProps) {
    if (Array.isArray(children)) {
        const [checkbox, ...restChildren] = children
        if (isValidElement(checkbox) && checkbox.props.type === 'checkbox') return (
            <li>
                <input type="checkbox" checked={checkbox.props.checked} readOnly />
                {restChildren}
            </li>
        )
    }

    return <li>{children}</li>
}
export default function PostLi ({children}: IProps) {
    if (Array.isArray(children)) {
        const [checkbox, ...restChildren] = children
        if (isValidElement(checkbox) && checkbox.props.type === 'checkbox') return (
            <li>
                <input type="checkbox" checked={checkbox.props.checked} readOnly />
                {restChildren}
            </li>
        )
    }

    return <li>{children}</li>
}

Yet another interesting thing to metion is that task lists selected as ul.contains-task-list was the only class name used through out the whole site style sheets. I overrided it as ul[role="listbox"] to avoid this.

Images

The image syntax in markdown is like ![alt](src) or ![alt](src "title"). I'd like to display the title as caption if present.

On default, MDX transform ![alt](src "title") to

<img src={src} alt={alt} title={title} />
<img src={src} alt={alt} title={title} />

Which means the title is almost not working, even Google spiders ignore title attribute in images. So, I decided to use the figure element to render images which has figcaption suitable for the title.

Then I need to override the default component in MDXRemote with a custom PostImage react component like

export default function PostImage ({src, alt, title}) {
    return (
        <figure>
            <img src={src} alt={alt} title={title} />
            <figcaption>{title}</figcaption>
        </figure>
    )
}
export default function PostImage ({src, alt, title}) {
    return (
        <figure>
            <img src={src} alt={alt} title={title} />
            <figcaption>{title}</figcaption>
        </figure>
    )
}

And pass them to the MDXRemote like

import PostImage from 'src/components/PostImage'

const components = {
    img: PostImage
}

<MDXRemote compiledSource={compiledSource} scope={scope} components={components} />
import PostImage from 'src/components/PostImage'

const components = {
    img: PostImage
}

<MDXRemote compiledSource={compiledSource} scope={scope} components={components} />

Unfortunately, content in mdx like below

![Placeholder image](http://placehold.it/800x400)

![Image with caption](http://placehold.it/500x300 "Image with caption")
![Placeholder image](http://placehold.it/800x400)

![Image with caption](http://placehold.it/500x300 "Image with caption")

will be rendered into

<p>
    <figure>
        <img src="http://placehold.it/800x400" alt="Placeholder image">
        <figcaption></figcaption>
    </figure>
</p>
<p>
    <figure>
        <img
            src="http://placehold.it/500x300"
            alt="Image with caption"
            title="Image with caption"
        >
        <figcaption>Image with caption</figcaption>
    </figure>
</p>
<p>
    <figure>
        <img src="http://placehold.it/800x400" alt="Placeholder image">
        <figcaption></figcaption>
    </figure>
</p>
<p>
    <figure>
        <img
            src="http://placehold.it/500x300"
            alt="Image with caption"
            title="Image with caption"
        >
        <figcaption>Image with caption</figcaption>
    </figure>
</p>

The problem is that p elements only accept inline elements (more precisely, phrasing contents) as children, which means put a figure in p might cause unpredictable behaviors.

The solution is to unwrap the images with a remark plugin called remark-unwrap-images.

It makes sense to put figures directly into articles. Then we style it like

article {
    figure {
        text-align: center;
    }
    figcaption {
        font-size: smaller;
        opacity: 0.5;
    }
}
article {
    figure {
        text-align: center;
    }
    figcaption {
        font-size: smaller;
        opacity: 0.5;
    }
}

and it looks like below

Placeholder image
Image with caption
Image with caption

Table

Take the tabel of components from MDX document as an example.

It has a sticky header, try scroll around the table. It's really tricky to style a responsive table, maybe we can talk about the details later.

TagNameSyntax
pParagraph
h1Heading 1#
h2Heading 2##
h3Heading 3###
h4Heading 4####
h5Heading 5#####
h6Heading 6######
blockquoteBlockquote>
ulList-
olOrdered list1.
liList item
tableTable
theadTable head
tbodyTable body
trTable row
td/thTable cell
codeCode```code```
inlineCodeInlineCode`inlineCode`
preCode```code```
emEmphasis_emphasis_
strongStrong**strong**
delDelete~~strikethrough~~
hrThematic break--- or ***
aLink<https://mdxjs.com> or [MDX](https://mdxjs.com)
imgImage![alt](https://mdx-logo.now.sh)

Blockquote

MDX makes blockquote footer easier to apply, it just takes a little tricks in styling and then

> Lorem Ipsum is simply dummy text of the printing and typesetting industry.
> Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

> Lorem Ipsum is simply dummy text of the printing and typesetting industry.
>
> Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
>
> <footer>Lorem Ipsum<cite>Lorem Ipsum</cite></footer>
> Lorem Ipsum is simply dummy text of the printing and typesetting industry.
> Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

> Lorem Ipsum is simply dummy text of the printing and typesetting industry.
>
> Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
>
> <footer>Lorem Ipsum<cite>Lorem Ipsum</cite></footer>

turns out to be like

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

Lorem IpsumLorem Ipsum

That's all about typography by now.

Published

Cover

Trace of Existence2016

Tags

Next.jsReact.jstypography