A New Website

#webdev #react #typescript

2023-06-29

You may or may not have noticed that the website has changed. I built the old site, samkwort.com, on a whim back in June/July 2020. I had pretty limited experience with web development and wanted to get some familiarity with technologies that I deemed to be quite accessible at the time. That old site was built using Pelican, a static site generator implemented in Python. I remember thinking at the time that I wanted to build a website without using any JavaScript. I was successful, though I'm not sure why I thought that. Fast-forward three years and that old site started to look pretty dated. Beyond that, I couldn't remember, nor did I document, how to add new posts to the website. I figured I might as well rebuild it using modern web technologies and take a deeper dive into the world of web development through JavaScript.

Goals

I wanted my new site to look better and be easier to create content for. I also wanted to learn a bit about JavaScript. My experience had been limited to writing a very small web scraper + JSON data server for an ESP32 project I've been working on recently. I also wanted to learn a bit more about React; I was introduced to React during an internship during the summer break--we were tasked with building a custom front-end for generating reports based on Jenkins CI runs. I didn't touch React, doing most of the backend Python-based data scraping. React seemed pretty cool at that time. I actually sat across from a senior dev who happened to be working on a front-end rewrite of an old Ember website. He kept saying that any new JavaScript projects should really be written in TypeScript. I added that to the list of things I wanted to learn.

Implementation

I did some research into modern web frameworks and settled on Next.js; its page-based routing and templating feature seemed quite intriguing, as well as its relationship with React. It also seemed to be a bit of an industry standard.

Layout

I started with the basic create-next-app template. I built the basic home-page, creating custom Header and Footer components. Then, I created a Layout component that would serve as the base for all my pages. Along the way, I picked up a little bit of Tailwind CSS--styling my pages this way felt really simple.

The Layout element code looks like this:

interface LayoutProps { children: React.ReactNode; } export default function Layout({ children }: LayoutProps): JSX.Element { return ( <div className="mx-4 md:mx-auto md:max-w-6xl flex min-h-screen flex-col items-left p-4 md:p-24 font-mono"> <Header/> <main>{children}</main> <Footer /> </div> ); }

I really like the way React feels like HTML; the idea of custom components is quite neat.

PostList

I wanted the home-page to have a list of the most recent posts. To do this, I needed to find a tool for parsing Markdown documents. After some research, I found the gray-matter package. This parsed the Markdown into a JSON object containing data and content fields; the data fields were generated using the YAML frontmatter of my Markdown documents.

I placed all my posts in a posts folder and wrote some code to generate a list of Gray Matter objects. These were then sorted by the date field. I used getStaticProps to ensure that the static blogPosts data was loaded before the page was rendered.

Here's an excerpt of the code:

interface HomeProps { postsData: Post[]; } export async function getStaticProps(): Promise<{ props: HomeProps }> { const postsData = await getPostData(); return { props: { postsData } }; } export default function Home({ postsData }: HomeProps): JSX.Element { return ( <Layout> <section> <h2 className="text-lg">Latest Posts</h2> <PostList postsData={postsData.slice(0, 5)} /> </section> </Layout> ); }

Dynamic routing with [post].tsx

Next offers dynamic routing, which makes it great for generating generic blog post pages for any given Markdown document. Setting this up is fairly easy; the Next documentation gives a pretty good example.

Once I had dynamic routes working and returning a basic hello-world type message, I started working on parsing the Markdown blog posts into an abstracted BlogPost component.

Markdown formatting

The essence of the BlogPost component is Markdown to HTML conversion. I found the perfect React component for this, React Markdown. This component takes Markdown formatted text as its child and converts it to HTML.

I also wanted the option to display code and math in my blog posts. To set this up, I followed the examples on the React Markdown GitHub page. The code highlighting used the Syntax Highlighter package, while the math formatting used remarkMath and rehypeKatex.

The final BlogPost component looks like this:

<ReactMarkdown className="markdown" remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]} components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( <SyntaxHighlighter {...props} style={irBlack} language={match[1]} PreTag="div" > {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code {...props} className={className}> {children} </code> ); }, }} > {post.content} </ReactMarkdown>

To style everything nicely, I used a dedicated stylesheet with Tailwind @apply directives. I think it came together quite well. Also, here's some math:

DRx4uxx  dx=4DRx3ux  dx=12DRx2u  dx\begin{align*} D\int_\reals x^4\,u_{xx} \; dx &= -4\,D \int_\reals x^3\,u_{x} \; dx \\ &= 12\,D \int_\reals x^2\,u \; dx \end{align*}

Hosting

My domain samkwort.com was previously hosted by Google Domains, which was recently sold to Squarespace. As such, I decided to migrate my domain to Cloudflare after reading some very positive reviews online.

Like my previous site, hosting is done through GitHub Pages. I used the Deploy to GitHub Pages Action.

Conclusion

JavaScript is weird. I definitely prefer coding in C and Python, but it was good to branch out a little bit. I'm a big fan of the new site; making content is super straightforward, and the design is a big upgrade.