hrtyy.dev

Hello World

Tech blog with Gatsby.js

Introduction

App.tsx
1import React from "react";
2
3const App: React.FC = () => (
4 <div>
5 HelloWorld
6 </div>
7)

Hi, I updated my blog using Gatsby.js.


Here, I will show you how to build it and how I write each posts. The list of what I wanted to do was the following.

  • Writing my post in markdown file

  • Highlighting source code written in the file.

  • Embedding JSX in the markdown file.

I'm writing this post in markdown file. But not just .md file, I used .mdx file. Using this format, I can write JSX in markdown file.


The Introduction section title is written as

# Introduction

By default, the # mark will not apply any styles. To apply styles, such as font size, bold or regular, I had to replace the default tag to my original component.

The line beginning with # mark will be converted to h1 tag, so I replaced the h1 tag to H1 component

H1.tsx
1export const H1: React.FC<PropsWithChildren<unknown>> = ({children}) => (
2 <h1 className="text-3xl font-bold">{children}</h1>
3)

The H1.tsx code is written with the following format.

this_post.mdx

```jsx:H1.tsx

// codes here

```

The list what I wanted to do is written in the markdown file(.mdx) with the following format.

// styled by tailwindcss

<ul className="px-8 py-4 list-disc font-bold">

<li>Writing my post in markdown file</li>

<li>Highlighting source code written in the file.</li>

<li>Embedding JSX in the markdown file.</li>

</ul>

Of course, the list can be written as

- hoge

- fuga

But, I used JSX here.
(By default the list is converted to ul and li tags. As I introduced, default tags can be replaced by components, so If you use markdown format, Ul or Li component may be needed.)


In this way, I could create fully customizable tech blog.

Setup Gatsby.js

First you need to setup these things.

  • gatsby-node.js to create page
  • gatsby-config.js to use some plugins
This official tutorial is good to complete them.


These are all plugins I used.

gatsby-config.js
1 plugins: [
2 'gatsby-plugin-typescript',
3 {
4 resolve: 'gatsby-plugin-mdx',
5 options: {
6 extensions: ['.mdx', '.md']
7 }
8 },
9 {
10 resolve: 'gatsby-plugin-postcss',
11 options: {
12 postCssPlugins: [require('tailwindcss')]
13 }
14 },
15 {
16 resolve: 'gatsby-source-filesystem',
17 options: {
18 name: 'src',
19 path: `${__dirname}/src/`
20 }
21 },
22 {
23 resolve: 'gatsby-plugin-graphql-codegen',
24 options: {
25 documentPaths: [
26 './src/**/*.{ts,tsx}'
27 ]
28 }
29 }
30 ]

Some plugins introduced in official tutorial such as gatsby-transformer-remark are not used here.

Create Page

We create pages. Assuming tree of this project is the following.

  • src
    • pages
      • anything
        • index.tsx
    • mdxPages
      • anything
        • helloworld.mdx
    • templates
      • TechPost.tsx

This is the script to create pages from mdx file.

In onCreateNode, paths are created passing "mdxPages" parameters to basePath. This means the post written in mdxPages/anything/helloworld.mdx can be accessed by "https://YOUR_DOMAIN/anything/helloworld"


In createPages, actual pages are created. Here, page metadata are fetched by graphql this is the feature of Gatsby.js.

"./src/templates/TechPost.tsx" are passed to createPage function. This means the content in mdx are rendered in TechPost.tsx component.

gatsby-node.js
1const {createFilePath} = require("gatsby-source-filesystem");
2const path = require('path');
3
4exports.onCreateNode = (({node, getNode, actions}) => {
5 const {createNodeField} = actions;
6 if (node.internal.type === 'Mdx') {
7 const slug = createFilePath({node, getNode, basePath: 'mdxPages'})
8 createNodeField({
9 node,
10 name: 'slug',
11 value: slug
12 })
13 }
14})
15
16exports.createPages = async ({graphql, actions}) => {
17 const {createPage} = actions;
18 const result = await graphql(`
19 {
20 allMdx(
21 sort: { fields: [frontmatter___date], order: DESC }
22 ) {
23 nodes {
24 fields {
25 slug
26 }
27 frontmatter {
28 title
29 abstract
30 }
31 }
32 }
33 }`);
34
35 const posts = result.data.allMdx.nodes;
36 posts.forEach(post => {
37 createPage({
38 path: post.fields.slug,
39 component: path.resolve('./src/templates/TechPost.tsx'),
40 context: {
41 slug: post.fields.slug
42 }
43 })
44 })
45}

In TechPost.tsx, mdx data fetched by graphql are just rendered as children of MDXRenderer.

TechPost.tsx
1import React from "react";
2import {graphql, PageProps} from "gatsby";
3import {TechPostQuery} from "../../graphql-types";
4import {BlogLayout} from "../layouts/BlogLayout";
5import {MDXRenderer} from "gatsby-plugin-mdx";
6
7export default ({data}: PageProps<TechPostQuery>) => {
8 // @ts-ignore
9 const {body, frontmatter} = data.mdx;
10
11 return (
12 <BlogLayout>
13 <MDXRenderer>{body}</MDXRenderer>
14 </BlogLayout>
15 )
16}
17
18export const query = graphql`
19 query TechPost($slug: String!) {
20 mdx(fields: {slug: { eq: $slug } }) {
21 body
22 frontmatter {
23 title
24 date
25 abstract
26 }
27 }
28 }
29`

BlogLayout component appears here. In this component, default tags are replaced to original component.

Code blocks surrounded by "```" in markdown file are converted to code tag.

So, I replaced it to CodeBlock for syntax highlighting.

BlogLayout.tsx
1import React from "react";
2import {MDXProvider} from "@mdx-js/react";
3
4export const BlogLayout: React.FC<PropsWithChildren<unknown>> = ({children}) => {
5 // replacing default tags to component
6 const components = {
7 h1: H1,
8 code: CodeBlock
9 }
10
11 return (
12 <div>
13 <MDXProvider components={components}>
14 {children}
15 </MDXProvider>
16 </div>
17 )
18}

H1 component is the component I defined in Introduction.

CodeBlock component is the following component. Basically, I used codes introduced officially. I added some changes to show line number and filename.

CodeBlock.tsx
1import React, {PropsWithChildren} from "react";
2import Highlight, {defaultProps} from "prism-react-renderer";
3import github from "prism-react-renderer/themes/dracula";
4
5type Props = {
6 className: string
7};
8
9export function CodeBlock({className, children}: PropsWithChildren<Props>) {
10 const matches = className.match(/language-([a-zA-Z]+)(:(.+))?/) ?? [];
11 const language = matches[1];
12 const fileName = matches[3];
13
14 return (
15 <div className="my-4">
16 {/*@ts-ignore*/}
17 <Highlight {...defaultProps} theme={github} code={children} language={language}>
18 {({className, style, tokens, getLineProps, getTokenProps}) => (
19 <pre className="px-4 py-4 text-xs bg-black rounded-md">
20 {fileName &&
21 <span className="block text-white text-center border-b border-gray-800 mb-2 pb-2">{fileName}</span>}
22 <div className="overflow-x-scroll overflow-hidden">
23 {tokens.map((line, i) => {
24 const unnecessaryLine = i === tokens.length - 1 && line.length === 1;
25 return unnecessaryLine ? null : (
26 <div {...getLineProps({line, key: i})}>
27 {<span className="text-gray-500 select-none pr-2">{i + 1}</span>}
28 {line.map((token, key) => {
29 const props = getTokenProps({token, key});
30 props.style = {color: props.style?.color ?? null};
31 return <span {...props}/>
32 })}
33 </div>
34 )
35 })}
36 </div>
37 </pre>
38 )}
39 </Highlight>
40 </div>
41 )
42}

Other languages like swift, go are available.

hello.go
1package main
2
3import "fmt"
4
5func main() {
6 fmt.Printf("Thanks for\n")
7}
main.swift
1import Foundation
2
3print("reading my first post!!")

Errors I encountered

gatsby-plugins-mdx: warn The GraphQL query in the non-page component "src/templates/**"

Solution: As mentioned here, I moved .mdx files to "src/mdxPages" directory from "src/pages".

MDXProvider with Typescript: Could not find a declaration file for module '@mdx-js/react'

Solution: Add declaration file to "src/types/mdx-js.d.ts" following this doc.

Memo

  • I used v2.0.0-next.8 for "@mdx-js/mdx", "@mdx-js.react" to use typescript.

  • To show icons cool using font awesome, I used gatsby-plugin-fontawesome-css plugin.