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:
Before we get started, here's the complete TechStack that I used:
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:
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",
}}
/>
</>
);
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.
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.