Hello World
Tech blog with Gatsby.js
App.tsx1import React from "react";23const App: React.FC = () => (4 <div>5 HelloWorld6 </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
#
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.tsx1export 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.
First you need to setup these things.
These are all plugins I used.
gatsby-config.js1 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.
We create pages. Assuming tree of this project is the following.
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.js1const {createFilePath} = require("gatsby-source-filesystem");2const path = require('path');34exports.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: slug12 })13 }14})1516exports.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 slug26 }27 frontmatter {28 title29 abstract30 }31 }32 }33 }`);3435 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.slug42 }43 })44 })45}
In TechPost.tsx, mdx data fetched by graphql are just rendered as children of MDXRenderer
.
TechPost.tsx1import React from "react";2import {graphql, PageProps} from "gatsby";3import {TechPostQuery} from "../../graphql-types";4import {BlogLayout} from "../layouts/BlogLayout";5import {MDXRenderer} from "gatsby-plugin-mdx";67export default ({data}: PageProps<TechPostQuery>) => {8 // @ts-ignore9 const {body, frontmatter} = data.mdx;1011 return (12 <BlogLayout>13 <MDXRenderer>{body}</MDXRenderer>14 </BlogLayout>15 )16}1718export const query = graphql`19 query TechPost($slug: String!) {20 mdx(fields: {slug: { eq: $slug } }) {21 body22 frontmatter {23 title24 date25 abstract26 }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.tsx1import React from "react";2import {MDXProvider} from "@mdx-js/react";34export const BlogLayout: React.FC<PropsWithChildren<unknown>> = ({children}) => {5 // replacing default tags to component6 const components = {7 h1: H1,8 code: CodeBlock9 }1011 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.tsx1import React, {PropsWithChildren} from "react";2import Highlight, {defaultProps} from "prism-react-renderer";3import github from "prism-react-renderer/themes/dracula";45type Props = {6 className: string7};89export 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];1314 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.go1package main23import "fmt"45func main() {6 fmt.Printf("Thanks for\n")7}
main.swift1import Foundation23print("reading my first post!!")
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.
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.