How I Originally Built This Site

An overview of how jimmy.codes was built with modern technologies and services.

7 min read View on GitHub

At the beginning of 2022, I decided I wanted to start blogging about coding. So in order to blog I needed a platform. I’ve also wanted to build a new portfolio under jimmy.codes. This all led to building this blogfolio. The goal for this was simplicity, performance and content powered by markdown. As a disclaimer, this is more of an overview rather than a how to guide.

Table of Contents

Technology Overview

Here are the main technologies:

  • Next.js: A React hybrid static & server rendering framework
  • React: Powers the UI
  • TypeScript: Provides types syntax for JavaScript
  • pnpm: A fast and efficient package manager
  • Cypress: A JavaScript Testing Framework used for e2e tests
  • Jest: A JavaScript Testing Framework used for unit tests
  • Testing Library: Testing utilities built around good testing practices
  • Tailwind CSS: A utility-first CSS framework
  • react-markdown: Component to render markdown powered by the unified
  • react-cool-dimensions: Performant react hook to measure size and handle responsiveness

Here are the services:

Next.js vs Astro

I first started with astro because for this use case it made perfect sense, so what is astro?

Astro is a new kind of static site builder for the modern web. Powerful developer experience meets lightweight output.

With features such as:

  • less javascript in the output
  • automatic partial hydration
  • the ability to leverage popular frameworks to build components
  • render pages from markdown

This meant that it was perfect to build a simple performant blogfolio while still using React. But it’s still in beta, so it’s lacking features such as easy image optimization, bug-free developer experience tooling, build customization, and others. Still, I’m very optimistic about Astro’s future and who knows, I might refactor this site to use Astro in the future.

Due to Astro lacking the features I mentioned, I went with the popular and proven framework, Next.js.

Markdown Content

Since Next.js does not come with out-the-box Markdown support like Astro we have to roll our own. Next.js gives us a great example powered by remark(like Astro) of how to do this but I wanted to leverage Next.js’s image component which gives features such as:

  • improved performance by serving the correct size with modern image formats
  • visual stability by preventing Cumulative Layout Shift
  • faster page loads by loading only when the viewport is entered with blur placeholders
  • image resizing on demand

To accomplish this I went with react-markdown due to it’s components feature, i.e

import Image from "next/image";
import ReactMarkdown from "react-markdown";
const renderers = {
img: (
image: DetailedHTMLProps<
ImgHTMLAttributes<HTMLImageElement>,
HTMLImageElement
>,
) => {
if (!image.src) return null;
return (
<Image
className="rounded-lg"
blurDataURL={image.src}
src={image.src}
alt={image.alt}
layout="responsive"
width={945}
height={645}
placeholder="blur"
quality={65}
/>
);
},
};
interface MarkdownContentProps {
content: string;
}
const MarkdownContent = ({ content }: MarkdownContentProps) => {
return <ReactMarkdown components={renderers}>{content}</ReactMarkdown>;
};

This allows replacing an image displayed with markdown, such as:

![Image](sample.jpg)

With <Image /> from next/image for optimization.

In addition, since React Markdown still leverages unified extensive plugin system. I took advantage by adding features such as:

Which easily is accomplished by using <ReactMarkdown />’s remarkPlugins and rehypePlugins props, i.e

import ReactMarkdown from "react-markdown";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeExternalLinks from "rehype-external-links";
import rehypeHighlight from "rehype-highlight";
import rehypeSlug from "rehype-slug";
import remarkUnwrapImages from "remark-unwrap-images";
import "highlight.js/styles/base16/material.css";
interface MarkdownContentProps {
content: string;
}
const MarkdownContent = ({ content }: MarkdownContentProps) => {
return (
<ReactMarkdown
components={renderers}
remarkPlugins={[remarkUnwrapImages]}
rehypePlugins={[
rehypeExternalLinks,
rehypeHighlight,
rehypeSlug,
rehypeAutolinkHeadings,
]}
>
{content}
</ReactMarkdown>
);
};

Image Optimization

I already mentioned Next.js’s image component but alongside Cloudinary features such as:

  • performant hosting
  • transformations with no visual degradation
  • automatically generate variants

And react-cool-dimensions to dynamically change sizes, i.e

import Image, { ImageProps } from "next/image";
import useDimensions from "react-cool-dimensions";
const ResponsiveImage = ({
src,
...rest
}: Omit<ImageProps, "layout" | "sizes">) => {
const { observe, width } = useDimensions<HTMLDivElement | null>();
return (
<div ref={observe}>
<Image {...rest} layout="responsive" sizes={`${Math.round(width)}px`} />
</div>
);
};

We get performant and high quality images at every size!

CI/CD Overview

CI CD Flow Diagram image

The entire CI/CD pipeline is powered by GitHub Actions with Vercel driving the deployment aspect.

Pull Requests

When a pull request happens Vercel immediately starts deploying the site under a preview url which allows for immediate visibility, automated tests and quick collaboration.

While that is happening GitHub Actions is doing a couple things:

These code quality checks fall under this projects’ status checks which prevents a pull requests from merging if they are not passing.

Deployments

Like I mentioned previously, deployments happen during pull requests under a preview url, but they also happen with any push to master which deploys to production. master is protected so any change needs to go through a pull request. In order words, a successful pull request must be made to release to production.

Automation Testing

Any time there’s a successful deployment, Cypress tests are executed with the base url from Vercel. Once those automated tests finish, they are uploaded to the Cypress Dashboard. This is accomplished by using the cypress-io/github-action, i.e:

on: [deployment_status]
jobs:
e2e:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: pnpm/action-setup@15569a497d6aff479ba1c47c859888e22a431052
- run: pnpm install --frozen-lockfile
- uses: cypress-io/github-action@v4
with:
install: false
record: true
env:
CYPRESS_BASE_URL: ${{ github.event.deployment_status.target_url }}
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

On top of the successful code quality checks needed for a pull request to be merged, these Cypress tests are also needed. This gives us an extra layer of confidence before deploying to production. 👍 🚀

Performance Testing

Since one of the goals was performance, I’ve also added Lighthouse as part of CI/CD pipeline to validate wether or not there’s been any performance degradation. And as a bonus we also get SEO, Accessibility and Best Practices validations.

In order to accomplish this, I used the Lighthouse CI Action which followed the same approach as the Cypress Action so it was a simple setup, i.e

lighthouse:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Lighthouse
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
${{ github.event.deployment_status.target_url }}
${{ github.event.deployment_status.target_url }}/blog
${{ github.event.deployment_status.target_url }}/about
${{ github.event.deployment_status.target_url }}/blog/posts/how-i-built-simple-blogfolio
uploadArtifacts: true
temporaryPublicStorage: true
configPath: .github/.lighthouserc.yml

Since this GitHub Action is powered by the Lighthouse CI, it offers extensive configuration.

Analytics

Using Fathom with Next.js is extremely simple, given you setup a custom domain first, all you need to do is use <Script /> component, i.e

{
process.env.NEXT_PUBLIC_FATHOM_KEY && (
<Script
src="https://wild-wind-innovate.jimmy.codes/script.js"
data-site={process.env.NEXT_PUBLIC_FATHOM_KEY}
strategy="afterInteractive"
/>
);
}

This will allow Next.js to take care of any optimizations and will give you all of Fathom’s features such as:

  • Fully GDPR, ePrivacy, PECR and CCPA compliant
  • Ability to see all visitors even those with ad blockers
  • Great SEO since the Fathom script loads fast
  • Analytics that don’t sell data which means no annoying cookie notices on this site
  • Optional uptime monitoring
  • Dashboard that can be private, password protected or public

For transparency’s sake, I made this site’s dashboard public.

Conclusion

There’s nothing game changing mentioned in this post and this is mostly a collection of cool things others have done. But it felt appropriate as my first post on my new personal site to be about how it came to be. With most of my personal projects, I will most likely refactor this with a new cool technology or service that comes along. For now Next.js, TypeScript, the Unified System, Fathom, Cloudinary and Vercel is a perfect combination to build a highly performant blogfolio site.

Thank you for reading ❤️.

Last updated on

Back to all posts