Jérémy
Gautrais

From HTML to static site generator

/ Published on: 

November 12, 2021

Next.js + Tailwind

In the past few weeks, I have updated my website from plain HTML, CSS + JavaScript to using a static site generator, Next.js. On the outside, the result is pretty much the same, but a lot has changed under the hood. My main reasons for choosing a static site generator (SSG) were the following:

  • Learning: I wanted to learn how SSG works and keep practicing with a JavaScript framework (React). I also started practicing with Tailwind CSS framework while rebuilding this website.
  • Scalability and maintainability: My portfolio was starting to become a HTML labyrinth with never-ending pages, in which retrieving information became more and more difficult. Using a framework make it easier to organize code and site content.
  • Blogging: I wanted to start a blog and have a more robust way of storing blog's data than putting it in HMTL raw content. With my current setup, each blog post is stored in markup language, and pre-rendered into HTML at build time.
  • Performance: Since pages are pre-rendered at build time, when a server request a file, the file is immediately returned without any processing. I can enjoy all the benefits from using React and Next.js development tools and still generate blazing fast static pages.

Why choosing Next.js ?

For such a small-scale project, all tech stacks could have been used. I have recently started a coding bootcamp at Wild Code School in Lyon and I am following there the PHP/Symfony track. Although I have learned React before, I wanted to keep practicing with this framework as it is the most popular on french market.

I also wanted to use specifically a static site generator, as all my content comes from static sources, all pages can be rendered at build time and served directly at each request, for improved performance.

My project to setup a personal blog was the perfect use case for me to practice with React and a static site generator. I choosed Next.js specifically, among others, since I had already started to deploy my static portfolio with Vercel, the team behind Next.js.

Tailwind

My website is an opportunity to try new tools and, while transitioning to Next.js, I decided to try a new CSS framework: Tailwind CSS. There was a slight learning curve as I tried to translate my CSS ideas into Tailwind specific class naming. But, generally speaking, I really enjoyed working with Tailwind, the documentation is clear and easy to navigate.

Dark mode is also easily set up, just enable the darkMode option in tailwind.config.js. I wanted to include a dark mode toggle so I used next-themes library for this. Tailwind is paired easily with next-themes although I had some difficulty to make next-themes use a class attribute on html element to store data about light/dark theme instead of data-theme attribute until I come across the relevant documentation. In the end, the setup of next-themes is pretty straightforward, just include the following in your _app.js file:

JSXimport { ThemeProvider } from 'next-themes';
function MyApp({ Component, pageProps }) {  return (    <ThemeProvider attribute='class' defaultTheme='system'>      <Component {...pageProps} />    </ThemeProvider>  );}

Finally, in your toggle component, you have to call the useTheme() method from next-themes (resolvedTheme variable is used throughout the whole app to store and retrieve info about current theme):

JSXimport { useTheme } from 'next-themes';
export default function Toggle() {  const { resolvedTheme, setTheme } = useTheme();
  const handleThemeChange = () =>    setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
  return (    <button onClick={handleThemeChange} aria='Toggle Dark Mode'>      Toggle    </button>  );}

Translations

Most of the content of my website is written in English, although, being born in France, I decided to provide translations for my 'Home' and 'About' pages. For this I am using i18n module, and more specifically next-translate library. The translations are easily set up following documentation. I also use the json translation files to store text content, in French and English, about my projects. With i18n, I can retrieve an array of translation with the option returnObjects:

JSXimport useTranslation from 'next-translate/useTranslation';
const { t } = useTranslation('namespace');const array = t('key', {}, { returnObjects: true });

I can then generate components for each project by iterating over the translations' array. Don't forget the empty brackets in the middle! They take optional query parameters but should still be present within the input parameters even if empty. It took me hours to figure out that the t function was not returning an array of translations for omitting to provide these empty brackets 🤯

MDX

The content of this blog is handled by MDX. It is an extension of the traditional markdown language, which allow to use JSX and integrate React components within markdown files. For example, in this blog, the images, links and code blocks are each rendered by custom components.
This is a great tool to provide richer blogging experience.
I use mdx-bundler to compile and bundle mdx files so that they can be interpreted in the browser.
For code blocks in particular, I use prism-react-renderer plugin. It allows me to use a custom color theme for syntax highlighting. Thanks to the proper class names it returns, I can define specific color for each code item.

Image gallery

Since I am producing voxel art and vector art, I included an illustration section where I display my creations. This consists in an image gallery, which makes extensive use of the custom Next.js Image component. It includes various optimizations to help dealing with images, which come in handy for a gallery with many illustrations.
In particular, from one image source, the component generates the appropriate image srcset and even provides images in webp format for browsers that support it. Let's demonstrate these features on a concrete example for my gallery. In the image below, you can see the files that are loaded for one of my illustration:

Next.js Image Component

On the left, on mobile view, you can see the first file loaded (row highlighted in green), the miniature, is provided in webp format and is only 3.8kB ! Once we click on the miniature, a lightbox is loaded with the original, full-sized original image which is 67.5kB in size (second row). That's almost a factor 18 size reduction, which allow the initial gallery page to load very fast. On the right, on desktop view, the miniature file size is slightly larger, 8.3 kB although still way lighter than the original size.
In addition to generating custom srcset, the Image component also includes lazy loading, so that miniatures are not loaded until they come close to the viewport to ease initial page load.


Final words

I want to mention and thank two creators who inspired me a lot and whose articles and tutorials were of great help while I was rebuilding this website:

 

After the rebuild, I end up using the following stack:

  • Next.js
  • Tailwind CSS (switched from CSS/Sass)
  • i18n (next-translate) library for translations
  • MDX for blog content
  • Deployed with Vercel

You can browse the code on Github.

/ Last updated: 

November 12, 2021