Building my personal website using Nextjs SSG (Static Site Generation) banner photo

Building my personal website using Nextjs SSG (Static Site Generation)

Recently I found myself with a lot of free time on my hand, so I took a look at the list of things I would like to complete in 2020 and found one thing that was there from 2019, my website. Here are my goals for the site:

  1. A blog to put my thoughts.
  2. Dark mode support.
  3. Faster load times.
  4. A good SEO.

Before we get started, here's the complete TechStack that I used:

  • Next.js
  • React
  • SCSS
  • Typescript
  • Devii Boilerplate - I changed a few things to make it fit my needs.

Faster load times & good SEO

Back in March Next.js released a new update v9.3, where they introduced a new feature SSG (Static Site Generation) which allowed Next.js users to generate Static sites from the Next.js projects, previously this was not possible if someone needs to build a Static site they had to rely on Gatsby for this or just use good old HTML & CSS, which most people would say is still the best for your personal website.

But I still went with the Next.js because of these things:

  • I like how Next.jS structures the project.
  • It's easy to build Blogs now that Next.js support SSG
  • I love ReactJS.

Faster load times

Load times for the pages can be improved drastically just by serving static files over a CDN, and since Vercel also provides CDN, we can leverage that to get faster load times.

Pages that use Static Generation and assets (JS, CSS, images, fonts, etc) will automatically be served from the Vercel Smart CDN, which is blazingly fast.

Loading SVG as React components

To improve loading speed even more, I also converted SVG images to React Components using React SVGR which reduces network request as the SVG is now rendered as part of the HTML.

Loading fonts from same domain

I was initially loading fonts from Google fonts, which was taking about 0.4s to load the fonts from thier server because of the round trip, I easily resolved it just by moving the fonts to static folder & then loading the fonts from the same domain.

Good SEO

NextJs by default provides good SEO because the content is available when search engines request pages to crawl unlike in client-side render web apps which have poor SEO.

SEO can be further improved by using a package like NextSEO or just by manually handling it by using meta tags, but I would recommend NextSEO as it provides a better syntax & omits few redundant tags.

import React from "react";
import { NextSeo } from "next-seo";

export default () => (
  <>
    <NextSeo
      title="My awesome website"
      description="My awesome website description"
      canonical="https://example.com"
      openGraph={{
        url: "https://example.com",
        title: "Open Graph Title",
        description: "Open Graph Description",
        images: [
          {
            url: "https://www.example.ie/og-image-01.jpg",
            width: 800,
            height: 600,
            alt: "Og Image Alt",
          },
        ],
        site_name: "SiteName",
      }}
      twitter={{
        handle: "@fayeedP",
        site: "@fayeed.dev",
        cardType: "summary_large_image",
      }}
    />
  </>
);

A blog to put my thoughts.

I used to post on Medium, but I didn't like how once you post on Medium it becomes their IP, that's why I decided to make my own blog.

Since v9.3 Next.js introduced a new API getStaticProps & getServerSideProps, here's what they do:

getStaticProps (Static Generation): Fetch data at build-time.

getStaticPaths (Static Generation): Specify dynamic routes to prerender based on data.

getServerSideProps (Server-Side Rendering): Fetch data on each request.

By using getStaticProps we can get the blog post data at runtime & pass it as props to Component, which then later uses it to generate static pages.

Since I used devii most of this was set up by default, so didn't need to do much work here, but I will still give a brief rundown of how it works.

All the markdown files live under the md/blog folder from the root directory of the project, each markdown must have these fields, the below example is from Create a chat app in minutes in Flutter.

title: Create a chat app in minutes in Flutter
subtitle: Finally a better way to build your chat functionality
published: true
datePublished: 1564943400249
author: Fayeed Pawaskar
authorPhoto: /profile.jpg
bannerPhoto: https://cdn-images-1.medium.com/max/2700/1*pYJQe00OSztV3p5gFeG0Cw.jpeg
canonicalUrl: https://fayeed.dev/blog/create-a-chat-app-in-minutes-in-flutter
tags:

- Dash chat
- Flutter
- Dart
- Chat App

Then in getStaticProps function, we get content of the markdown files using gray-matter and then properly structure it before passing it to the component as props.

This makes a very easy to add more articles later as you just have to write markdown and push and Nextjs takes care of it.


Darkmode support

I initial used Styled Components as it comes with default Theming support, which was working pretty great initially but when I deployed to the Vercel and it generated Static site I started facing some problems with, styles were not copied to the articles pages, it was working as expected on other pages but for some reason not on blogs.

I tried fixing this issue using Styled Components's ServerStyleSheet Class, in _document.ts which collects all the styles from the parent App tag and pass it a style tag. This solved the problem but introduced a new one, whenever I go back to the index page from the blog post page it would completely break the styles.

import Document from "next/document";
import { ServerStyleSheet } from "styled-components";

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      };
    } finally {
      sheet.seal();
    }
  }
}

Finally, I decided to move everything from Styled Components to SCSS which solved all the problems but I would have loved to use Styled Components.

To support light & dark mode I wrote a small react hook that saves current mode to localStorage and adds a new class to body tag light-mode or dark-mode depending on the current mode.

  export const darkMode(initialValue: boolean) {
    const [darkmode, setDarkmode] = useState(initialValue);

    const toggle = (value: boolean) => {
      document.body.classList.remove(!value ? "dark-mode": "light-mode");
      document.body.classList.add(value ? "dark-mode": "light-mode");
    }

    useEffect(() => {
      const value = localStorage.getItem("darkmode");

      if (value) {
        toggle(value);
      } else {
        document.body.classList.add(initialValue ? "dark-mode": "light-mode");
      }
    }, []);

    const toggleDarkmode = () => {
      localStorage.setItem("darkmode", !darkmode);

      setDarkmode(!darkmode);

      toggle(!darkmode);
    }

    return {darkmode, toggleDarkmode};
  }

And then based on the body tag class, I would then change the styles for the components.

body.light-mode {
  background-color: var(--light-body);
  color: var(--light-text);
}

body.dark-mode {
  background-color: var(--dark-body);
  color: var(--dark-text);
}

Thanks for reading, if you have any questions or suggestions get in touch with me on twitter.