Static Pages served from the Vercel edge network with 0.8 seconds to interactivity
Notion as a content management system (CMS)
Motivation
I’ve used a variety of content management systems in the past such as Joomla, Wordpress and Wix. However, I never ended using one consistently to keep my website upto date with new content. I discovered that the core problem was that their tools were un-intuitiveas it forced me out of my workflow to do something simple, such as updating a post. A few years ago, I integrated Notion into my workflow where I used it to take class notes, journal and work on personal projects. The reason why myself and the rest of the Notion Cult loves using it is that it simplifies our workflows and provides a hierarchical structure to all our documents - while ensuring that it is all catalouged.
The Notion cult
When I bought my domain, I wanted to use a CMS which required the least friction to upload content. I discovered a free tool, Fruition which enabled me to serve my notion page via my domain. Fruition uses a Cloudflare JavaScript worker to display all the contents of the notion site but with the routing was decoupled from the notion domain. So instead of https://www.notion.so/devinda/This-Website-361e85f3bef14f1aa4c8ba60ae4b4c88 , the URL bar would display devinda.me/This-Website
This setup (visit the old site) worked for a few years but there were a few issues that accumulated which ultimately led me to building this site.
Long load times - The JavaScript worker just took too long to display my site, sometimes over 30 seconds for complete page render
Poor customization - I wasn’t able to place blocks exactly where I wanted them or have finer control of the information displayed
Potential for breakage - Changes by the Notion team sometimes broke the JavaScript worker which would require me waiting till the Fruition script was fixed
Sketching on Figma
Here are some of my initial project designs from Figma. I’ve been studying UX design, and in this project I used Design Thinking. This is an iterative process where I design a feature and then build it before moving onto the next feature.
Research
Going into this project, I wanted to explore other implementations of Next.JS and the Notion API. I needed a project that uses the newly released official Notion API and I found these two projects -
I then inspected their code to be inspired by their implementation architecture and understand how they were transforming content from the Notion API to website components.
Creating Pages
/index
The most challenging part of this page was ensuring that my name was properly displayed on different devices. The length of my last name meant that work was concentrated here to ensure that the responsiveness and image gradient looks correct across devices. TODO: Show homepage on different devices.Next.JS + Vercel has a different approach to implementing images which optimizes the load time of images. I noticed that with my default settings, the images would take too long to load. This meant that I had to do a bit of iterative tweaking to discover how to show images correctly on the card components. This involved playing around with the width and height attributes along with image sizing until I found a fitting compromise between the image size, resolution and download speed.
/projects/[slug]
I had to create my own API transformer that could convert JSON objects from the Notion API to React components while ensuring it was styled properly with Chakra UI. I built the transformer with a minimalist approach - implement the least required to create a functional project page. The first components were quite straight forward to implement - headings, paragraphs and images.Lists were a problem since the API did not mention when a list started or ended. Here I realized that I needed to create my own Typescript types which could map the JSON objects to my internal representation.
Likewise, inline text formatting and image captions were blocking points which I solved via my own types. With a definitive type in place for each of these complex blocks, it eliminated much of the logic needed to style the React components.
As of now, this site supports
Headings
Paragraphs
Lists
Images and captions
Inline formatting
Notion API Integration
For this project, I’m using the official Notion API. While it just recently came off beta, the typescript support was quite poor which was why I had to implement my own private types. TODO: Infrastructure image
My notion pages are being accessed via my private integration that allows me to access content without having to make my notion document public. The integration is then consumed on the Vercel edge nodes to create and update the static pages.
Optimizations
In order to improve the performance of the site and to achieve a better Lighhouse performance score, I implemented optimization options offered by Next.JS and Vercel
Image Optimizations
A big problem was cumulative layout shift. This is when an image starts loading, the layout of the page will change. Content below the image will start shifting down distracting the user. When using the Image component, it is possible to specify the desired with and hight of the image to prevent this. However, this means that there will be a large empty box while the image is loading. This was solved via blurred placeholderes that provide visual context while the actual image is loading. he cu
These images are hosted on cloudinary, which is a dedicated image cdn and is cached by image size on Vercel edge nodes. Since these images are cached by image size, only the most appropriatly sized image is downloaded. For example, while the original image has a resolution of 4k, when the web page is loaded on a mobile device, it only downloads a 360p sized image. As a mobile device would not benefit with a 4k sized image, this optimizations saves data for the end user and improves the downoad speed of the image.
Next.JS Optimizations
Instead of the old website, where the request would go all the way to the notion servers to get content data, with this current website, pages are statically created (Static Generated Pages). The benefit is that, when visiting this page, only an HTML page needs to be served. The content does not need to be hydrated and this allows pages to be served at blazing speeds.Sometimes, page content needs to be updated after building the site. Instead of rebuilding the static pages every time, incremental static regeneration is implemented. Every 60 seconds, Next.JS will check if the page content is different to what it currently is. If it is, it will rebuild just that page and serve it. With this implementation, a change on a notion document will be reflected on the website in under two minuites.TODO: Image of old website and new websites light house score