Contents
There's a bit of trend that I rebuild my site around this time of year, not sure why. The most recent incarnation was my first foray into React having previously migrated from Wordpress (yuk).
The React based architecture is hosted on AWS in S3+Cloudfront and represented a big evolution towards a more serverless architecture and frankly, cheaper run costs.
The core function of my site is that its largely a static collection of blog posts and some social integration with Flickr and Youtube, plus some fairly lightweight data functionality for tunes and run events. For this, React was (and still is) largely ideal - its all lightweight rendering/construction that works well with client side JS rather than expensive compute intensive server side rendering like PHP/Wordpress.
The blog functionality was based around markdown, and I've got used to writing in markdown language and publishing. The markdown was rendered at runtime which gave me a few issues with posting links on social media etc, and with SEO/crawling.
The tech world never stands still, and this problem has been eloquently tackled with Jamstack. Its a further evolution in the web app / web site architectures that moves away from heavy server side rendering and hosting, to lighter client side rendering and pre-rendering (ie render at build time rathe than runtime)
Enter; GatsbyJS
I'd been aware of GatsbyJS for a while and had experimented earlier on in the year, but didn't have the motivation to take it further.
For the unacquainted, Gatsby is a framework that sits atop of React, and allows you to design and author pages in much the same way as you'd do with React - ie create components, assemble components into pages/views - all the good Atomic design principles.
The boon is that Gatsby can pre-render the pages to flat HTML at build time - this makes them easier to set meta tags and clearly faster.
For the dynamic functionality this wasn't a great benefit, but for blogs, it was a no-brainer as it meant fast load times, proper meta tagging with OG, twitter etc and much better support for SEO.
From a content editing workflow point of view, the flow is largely unchanged:
- Author the content in markdown and run a local instance on my laptop during authoring to check formatting etc.
- Once ready to publish, build the site and push the contents into S3
The change is subtle; the pure React approach, to develop locally:
npm run start
To publish:
npm run build
aws s3 sync build s3://$SITE_BUCKET --delete --quiet
aws cloudfront create-invalidation --distribution-id $CLOUDFRONT --paths '/*'
The new Gatsby way, to dev local:
gatsby clean && gatsby develop
Then to build and push to S3:
gatsby clean && gatsby build
aws s3 sync build s3://$SITE_BUCKET --delete --quiet
aws cloudfront create-invalidation --distribution-id $CLOUDFRONT --paths '/*'
Plugins & GraphQL
Gatsby is all about plugins (and there is a rich collection of these) and GraphQL. The later takes a bit of getting used to and I'd highly recommend this article on it.
Most of the plugins I've used work with the gatsby-transformer-remark plugin, and control how you render markdown into HTML, eg code snippets, handling images and embedded YouTube videos etc.
Architecture
The infrastructure architecture and build/pipeline approach remain unchanged, as does the core JS based ecosystem. The subtle change is simply in the libraries used.
Infrastructure Architecture
The site is based around AWS hosting via S3+Cloudfront - ie static content hosting. The resources are managed via Terraform.
The hosting architecture is broadly the same as the IoT WebUI, with the site content held in an S3 bucket, and served through Cloudfront.
One gotcha was the default behaviour of Cloudfront, which handles index.html in the root folder only, hence www.site.com -> www.site.com/index.html, but not in subfolders, so www.site.com/page1 -> www.site.com/page1/index.html doesn't work.
To resolve this we use Lambda@Edge to handle the pre-pend. Good article on this posted by Ximedes
The result is a very simple Lambda@Edge function the fires on the Origin Request event from CloudFront:
'use strict';
exports.handler = (event, context, callback) => {
console.log(event)
const request = event.Records[0].cf.request;
const uri = request.uri;
if (uri.endsWith('/')) {
request.uri += 'index.html';
} else if (!uri.includes('.')) {
request.uri += '/index.html';
}
callback(null, request);
};
Typically I use Serverless framework for managing the deployment of Lambda functions, however, its a bit of a faff with Lambda@Edge and tries to create the Cloudfront distribution, rather than append to an existing one. This is something I'll look at later, but in the interim I've just manually deployed the Lambda function (shudder).
Application Architecture
The application is based around the following core technologies:
- React (16.12)
- Gatsby (2.24)
- Bootstrap (4.5)
The quite a few plugins to make things easier, including:
- Helmet (for meta tagging)
- Moment for date formatting
- Axios for API calls
- Font Awesome for icons
- Google maps for React, for the running map.
- ABCJS for the tune database manuscript rendering.
The structure of the site its build around components; blog posts, blog lists, youtube lists, scaffolding etc, which are assembled into pages.
Images
Another great find with Gatsby is how it handles images and how you can manipulate them with the image sharp renderer. In short you can chuck in hi-res images into your images folder, reference them in the markdown and control breakpoint sizing and aspect ratios so that images are resized/baked during build. It means the right image resolution is then served on page, so no client resizing. Again, resulting in blazing fast page load times.
MDX
So this a new one on me - MDX is to Markdown what JSX is to HTML/Javascript. It allows you to write markdown content, and pepper that content with JSX or React components. At first, it sounds like a bit of a disaster as you are mixing structure and content. But in reality it just extends the principles of React's atomic design and component architecture.
I've used MDX and regular markdown all over the site, and particularly:
- MDX - For static pages like Home, Running, Software, Hardware, etc
- "Regular" markdown - For blog posts
The simple reason behind this is that blog posts are designed to be simple - just blog content, no distractions. Whereas the other pages are typically a meld of blogs lists, youtube links and content.
Here's an example of MDX in action with the photography page. Note the mixture of regular markdown content, and the various JSX components for Flickr, Bloglists and YouTube video lists.
import Layout from "../components/layout"
import BlogList from "../components/bloglist"
import YouTubePlaylist from "../components/youtube-playlist"
import FlickrCarousel from "../components/flickr-carousel"
<Layout logo="camera-retro" title="Photography" description="Photography projects">
I take pictures of stuff, used to do loads, now it tends to be more holiday snaps or GoPro running footage.
---
<FlickrCarousel title="Flickr"/>
---
<BlogList category="photography" full="true" title="Photography posts"/>
---
<YouTubePlaylist playlistId="PLvBy2iVVlTu2gEuSvpqIIN0Hi_U4Bv-3t" full="true" title="YouTube"/>
</Layout>
Summary
I like it!. it probably took around 3-4 days to affect the rebuild, with a few additional cosmetic tweaks and enhancements along the way whilst the "bonnet was open".
For websites rather than web applications, its a no-brainer choice, and I see more and more commercial sites now starting to leverage the Jamstack architecture. But even for web-apps, I can see a more blended micro-frontend based architecture that uses both regular React and Gatsby.
The ponderable though is around the future of CMS and particularly how this would fit into a corporate scenario where content editors are used to the likes of Wordpress and Drupal. If we can change that, we'd save a lot of trees by removing huge amounts of compute power from data centers running traditional server side hosting.