Something I can be proud of ?

February 2024
·
0000 views

Since 2024, I have experienced significant changes both personally and professionally.

In light of these changes, I decided to overhaul my website and confront a stigma that has followed me for as long as I can remember; this stigma is the driving force behind this redesign. I will also discuss the approach, technologies, and choices I made to create a site that I am pleased with and of which I am not ashamed.

The perpetual struggle

At the heart of my journey in the world of programming lies a silent but consuming struggle: shame and imposter syndrome.

For me, these emotions are not simply abstract concepts, but constant companions that have shaped every line of code I have written.

With every quick alt-tab when someone approaches my screen, with every evasive answer when I am questioned about my work, these feelings are there.

Even though I am convinced that these emotions are universal, I am also convinced that the way they manifest is unique to each individual. For me, they manifest in my relationship with my code.

I am constantly rewriting, refactoring, reorganizing; and I am always dissatisfied; I am always haunted by the feeling that my code is not good enough, mediocre, and that I am not good enough to produce it.

Breaking the cycle

A few years ago, when I considered creating my own personal website, I aimed to design an immersive experience for visitors using Three.js to create a 3D universe. However, I quickly realized that I didn't have the necessary skills to bring this vision to life.

Faced with this realization, I had to lower my ambitions. Nevertheless, the idea of creating something dynamic and interactive never left me. Despite my efforts, the website I eventually managed to create didn't evoke the pride I had hoped for. Every time someone asked to see it, I felt a certain embarrassment and was not truly satisfied with the result.

It remained online for a few years, but I never took the time to update it, and it became obsolete.

A few years later, I decided to start the project from scratch. Observing the work of other creators, especially that of Paco Coursey and Emil Kowalski, I realized that I didn't necessarily need to design a complex site with animations everywhere to create a pleasant user experience. I found a certain beauty in simplicity, and it was this approach that I adopted this time around.

Thus, after many adjustments and reflections, I finally managed to create a website that I am proud of.

Next.js — All-round simplicity

I wanted to create a website that prioritized simplicity, focusing on content and ease of maintenance, that's why I chose to use Next.js, a framework I've been familiar with for several years now and find highly enjoyable to work with.

Next provides a lot of features out of the box, such as server-side rendering, static site generation, and API routes, which makes it a great choice for a blog, but not only, as it also offers a bunch of components for every use case, like SEO, Image, Font and so much more — Really, take a look at the docs

OG image generation

One of the features I wanted to implement was the automatic generation of Open Graph images for each article. Using @vercel/og, this was a breeze to implement, with a simple opengraph-image.tsx file in the pages directory, I can generate an image with the title, publication date, reading time, and number of views of the article.

Open Graph image

export const runtime = "edge"
 
export const size = {
  width : 1200,
  height: 630
}
 
export default async function Image({ params }: { params: { slug: string } }) {
 
  return new ImageResponse(
    (
      <div
        style={{ backgroundImage: `url(${image})` }}
        tw="flex flex-col justify-center p-12"
      >
        <p tw="mb-6 italic leading-tight text-6xl text-neutral-100">{ title }</p>
      </div>
    ), { ...size, }
  )
}
I use the tw prop to style the components with Tailwind CSS

Content management — MDX

By combining Markdown and JSX, MDX is a powerful tool that allows me to write content in a familiar format while also providing the flexibility to create custom components for more complex content.

I had heard about Contentlayer, a tool that allows you to manage content in a structured way using MDX, so I tried it out, and I must say that I was pleasantly surprised. I particularly appreciated the ease with which the schema can be modified, as well as the robustness of its type-safe configuration.

Unfortunately, the project seems to have been abandoned. After reading Lee Robinson's article on the redesign of his blog in 2023, I was inspired by his desire to unify and simplify his content management approach.

So, I decided to follow this path and manage the content of my site myself.

I created a simple structure for my articles, which I store in the content directory, and I use the next-mdx-remote package to render them on the client side.

src/lib/writings.ts
import fs from "fs";
import path from "path";
 
function getMDXFiles( dir: string ): string[] {
  return fs.readdirSync( dir ).filter( ( file ) => path.extname( file ) === ".mdx" );
}
 
function readMDXFile( filePath: string ) {
  return parseFrontmatter( fs.readFileSync( filePath, "utf-8" ) );
}
 
export function getWritings() {
  return getMDXData( path.join( process.cwd(), "content" ) );
}
 
function getMDXData( dir: string ) {
 
  return getMDXFiles( dir ).map( ( file ) => ({
    slug: path.basename( file, path.extname( file ) ),
    ...readMDXFile( path.join( dir, file ) );
  }) as Writing[] )
 
}

Now, we can easily retrieve the content of an article and display it on the page using the getWritings( ) function. While we don't have all possible features, I prefer the freedom to do what I want and to adopt specific solutions for each point and need. If you'd like to take a look, my site is open source on GitHub

app/writings/[slug]/page.tsx
export default function Writing({ params }: { params: { slug: string }}) {
 
  const writing = getWritings( ).find(( writing ) => writing.slug === params.slug );
 
  if ( !writing )
    notFound( );
 
  return (
    <article>
      <header>
        <h1>{ writing.metadata.title }</h1>
        <p>{ writing.metadata.publishedAt }</p>
      </header>
      <main>
        <MDXRemote { ...writing.content } />
		  </main>
		</article>
	);
}

Each article is stored in a separate file in the content directory, and the metadata is defined at the beginning of the file using a YAML front matter block.

content/something-i-can-be-proud-of.mdx
---
title: Something I can be proud of ?
publishedAt: "2024-02-02"
---
 
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
 
## A heading
 
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur.
 
<PreFragment>
  console.log( "Hello, World!" )
</PreFragment>

Monochrome — A timeless choice

I wanted to create a design that would stand the test of time, so I chose a monochrome palette with a touch of color for the accent. I find that this choice allows the content to take center stage while providing a pleasant reading experience.

For the typography, I chose Geist, a modern and elegant font that is easy to read and provides a touch of sophistication to the design.

Syntax highlighting is provided by Rehype Pretty Code, a unstyled syntax highlighter that uses Shiki under the hood, and Vesper, a beautiful theme created by Rauno Freiberg that I find particularly pleasant to read.

Code blocks are displayed in a simple and unobtrusive way, with a light gray background and a subtle border to separate them from the rest of the content. I added a small logic to the code block with a custom component <PreFragment> that allows me to display something before or after the code block, like a custom component.

I only use CSS selectors to create the junction between the code block and the <PreFragment> component.

<PreFragment>
  <Maze width={ 10 } height={ 10 } />
</PreFragment>
```tsx title="We can add a title to the code block"
function Maze({ width, height }) {
  [...]
}
```
<PreFragment>
  A Fragment can be used to display a component before or after the code block.
</PreFragment>

The code block is displayed as follows:

┌─┬─────┬─────────┬───────┐ │ └─┐ ╷ └─╴ ┌───┐ ├─╴ ┌─╴ │ ├─┐ ╵ ├─────┴─┐ │ │ ╶─┤ ┌─┤ │ └───┤ ╷ ┌─╴ │ │ └─╴ │ ╵ │ │ ┌─╴ │ │ │ ┌─┘ └───┬─┴─╴ │ │ ├───┤ │ │ │ ┌───┐ │ ┌───┤ │ ╵ ╷ ╵ │ │ ├─┘ ╷ ╵ │ │ ╶─┤ ├───┴───┘ │ ╵ ┌─┴─╴ │ │ ╷ │ │ ╶─┬───┬─┴───┤ ╶───┤ └─┤ │ ├─╴ │ ╷ │ ╶─┐ └───╴ ├─╴ │ │ │ ┌─┘ └─┘ ┌─┴─────┐ │ ╶─┤ │ │ └─┬───┐ │ ┌───╴ │ └─┐ ╵ │ │ ╷ ╵ ╷ └─┘ │ ╶───┴───┴─╴ │ └─┴───┴─────┴─────────────┘

We can also add a title to the code block
function Maze({ width, height }) {
  [...]
}

A Fragment can be used to display a component before or after the code block.

Some other little things

  1. Light / Dark Mode — Initially, I was inclined towards a dark theme for my website, purely based on personal preference. However, upon deeper reflection, I recognized the importance of ensuring optimal readability and a clear interface.

    The implementation is indeed very simple with next-themes; just two lines are needed. However, I was looking for something more sophisticated and gradual. This is where the View Transitions API comes in, allowing me to create a smooth and elegant transition between themes. Unfortunately, this API is still relatively new and is not supported by all browsers.

    "use client"
     
    import { flushSync } from "react-dom";
    import { useTheme  } from "next-themes";
    import { useState  } from "react";
     
    export function useThemeSwitcher( ) {
     
      const { theme, setTheme } = useTheme( );
      const [ isThumbnail, setIsThumbnail ] = useState( true );
     
      const handleViewTransition = ( ) => {
     
        if ( !document.startViewTransition )
          return setTheme( theme === "dark" ? "light" : "dark" );
     
        document.startViewTransition( ( ) => {
          setTheme( theme === "dark" ? "light" : "dark" );
          flushSync( ( ) => setIsThumbnail( ( prev ) => !prev ) );
        });
     
      }
     
      return { theme, isThumbnail, handleViewTransition };
     
    }
  2. Partial Pre-Rendering — 99% of the content on my site is static, and can be pre-rendered at build time. However, I wanted to keep the ability to update the view count of each article in real time.

    <article>
    	<header>
    		[...]
    		<Suspense fallback={ <span>0000 views</span> }>
    			<Views slug={ writing.slug } />
    		</Suspense>
    	</header>
    	[...]
    </article>

    PPR is the perfect blend of static and dynamic content, allowing me to maintain the performance benefits of static site generation while also providing real-time updates for specific elements. When a user visits a page, the static content is displayed immediately, and the dynamic content is streamed in as soon as it is available.

    const nextConfig = {
    	experimental: { ppr: true },
    };
    Enable Partial Pre-Rendering

    However, as indicated, this is an experimental feature. I'll provide you with some resources, the Vercel blog post on this topic and a very good example.

What's next ?

One of the things I absolutely must avoid to not fall into this kind of disgust towards what I've done is to spend too much time on it and to pursue perfection at all costs. So, I will stick with this version and publish it.

However, there are still some elements I would like to add, such as...

  • A navigation bar within the articles: I had started working on an initial design, but I'm not sure I really like it.

  • The use of Sandpack to allow readers to edit and test the code directly in the browser.

  • Adding a reading mode that blurs all the elements of the page except the hovered element, to focus on the content.

  • A comment system: I'm not sure if I want to add one, but it could be interesting to have feedback from readers.

  • Finally, I'd like to improve the code blocks, be able to fold the content, use tabs to display multiple related codes without taking up too much space, etc.

But for now, I'm happy with what I've done, and I'm proud to share it with you — Thank you for reading.