VSCode, Markdown, and NextJS Blogging Stack

Time to read: 7 minutes

This blog is written 100% in VSCode, including all the diagrams and images.

My setup is optimized for a fast flow, and a monorepo style by keeping me in my editor.

Text

Text is added to markdown files. The markdown files are converted to HTML using remark and gray-matter.

Example blog:

---
title: VSCode, Markdown, and NextJS Blogging Stack
excerpt: >-
  This blog is about how I built this blog site. It is focussed on a fast flow for
  both writing and drawing/annotating using a single editor, VSCode.
date: '2021-01-23T17:51:54.096Z'
tags:
  - nextjs
  - vscode
---

# VSCode, Markdown, and NextJS Blogging Stack

This blog is written 100% in VSCode, including all the diagrams and images. Lets describe how.

Images

For images, I use a drawio plugin for vscode. This works really well for diagramming as if files are named *.drawio.png you can embed them directly into your markdown.

![diagram showing architecture](/assets/blog/serverless-search/architecture.drawio.png)

Looks like this diagram showing architecture

Code Snippets

Most of the code that is shown in snippets is written in normal files. This reduces duplication, and errors. So most of the code is a live piece of code that is referenced using embedme. This CLI runs on a hook to compile markdown files.

Lets have an example, this is the relative path to the page you are currently viewing ../pages/posts/[slug].tsx.

To embed it, I add the path as the first comment to a block of code.

Now I run yarn embed and the below is the outcome.

// ../pages/posts/[slug].tsx

import { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
import markdownToHtml, {
  getPostFileNames,
  getPostMatter,
  getPostSlugs,
} from '../../lib/api';
import 'highlight.js/styles/atom-one-dark.css';
import styles from './Slug.module.css';
import { calculateReadTime } from '../../lib/utils';

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = getPostFileNames();
  const slugs = getPostSlugs(posts);
  const paths = slugs.map((slug) => ({ params: { slug } }));
  return {
    paths,
    fallback: false,
  };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const { data, content } = getPostMatter(params.slug as string);
  const innerContent = await markdownToHtml(content);
  const readTime = calculateReadTime(innerContent);
  return {
    props: {
      data,
      content: innerContent,
      readTime,
    },
  };
};

export default function Post({ data, content, readTime }) {
  const dateTime = new Date(data?.date);
  return (
    <div className={styles.lowPadding}>
      <Head>
        <title>{data?.title}</title>
        <meta name="description" content={data?.excerpt} />
        <meta name="keywords" content={[...data?.tags].join(', ')} />
        <meta name="author" content="Simon Reilly" />
      </Head>
      <h1>{data?.title}</h1>
      <div className={styles.titleRow}>
        <em>Time to read: {readTime} minutes</em>
        <time dateTime={data?.date}>{dateTime.toDateString()}</time>
      </div>
      <hr />
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </div>
  );
}

It also supports line numbers...

// ../pages/posts/[slug].tsx#L23-L30

const { data, content } = getPostMatter(params.slug as string);
const innerContent = await markdownToHtml(content);
const readTime = calculateReadTime(innerContent);
return {
  props: {
    data,
    content: innerContent,
    readTime,

Now I can write freely, alter and test code, without copying it across to blogs. This means most of the code snippets are tested and used, so less likely to become stale.

Search Indexing

The search feature for this blog is a pre-computed index. There is no overhead added to indexing pages. Instead the index will insert the gray-matter from each blog on build.

I have dedicated a whole blog to this here