How I built this silly little website
Intro
My previous site was over 2 years old - 200 years old in dev terms - it was a single, basic page, built statically in Vue JS, and hosted on Netlify. It had no blog - just a very simple, small space on the internet. It served its purpose: it was 'somewhere' to point people when asking if I had a website.
Once I put that previous site live, I didn't touch it, it gathered dust as I focussed on client work - I was busy improving my skills, in the real world, which was far more important.
Eventually, there came a time, for me it was 2 years, when doing a project entirely for myself was all I wanted to do. So that's what I did! Quite selfishly, I turned down work to free up a month, or two, in the calendar - I was feeling a bit burnt out anyway, so some time off was necessary.
I then, almost immediately, joined a start-up on Shopify, going totally against what I just said, and was inundated with new challenges that turned my attention away from my own website. For goodness sake, Matthew! I wrote about it here.
Learning
Of course, rebuilding the site with the same tech as before would be a missed opportunity - staying stagnant in this industry is quicksand! I wanted to learn typescript, use an HSL colour palette, have a headless CMS back-end for a blog (I wanted to improve my written communication skills), and generally just have fun with the site.
I wanted to use React / Next JS, instead of Vue - I love both, but I 'grew up' using Vue and was getting a bit bored of it.
Inspiraton
I use Twitter as a platform for scrolling through memes. Memes? I meant "for keeping up with the latest design and development news and trends" π The most important thing in life is balance.. π§π»
Now and then, a Twitter thread would appear with a subject along the lines of "What are the best sites you've come across recently?" followed by lots of replies from other users sharing their favourites. Each time, I would rummage through replies and keep note of nice ones.
Here is a couple I saved:
I cleaned my workspace and went for a run.
Design
I then mocked out a rough idea of how I would like the site to look, and I mean rough. It was enough to get excited about starting. As this was a personal project, I could make design changes as the site came together. It was liberating because, in the day-to-day, all I do is stick to signed-off designs. I was doing it willy-nilly!!
Front-end
React, Next App
I followed the Create Next App Documentation which included running the following install script:
yarn create next-app --typescript
I utilised the --typescript
argument to set the project to support typescript by default, lovely!
Note: I hadn't used much Typescript before building this site - although I had experience with React "PropTypes" and Vue props: {}
, so I had experience with checking data types that were passed to components.
Once installed, I could run a yarn dev
and the fresh React project was served locally at http://localhost:3000
CSS Modules
My entire development career had consisted of using BEM (Block, Element, Modifier) methodology for CSS. I wanted to try out CSS modules, which keep each CSS file local to the component it is imported to and, when the files are compiled, gives the class a unique name to avoid any global clashes.
HSL Colours
I hadn't ever used hsl()
as a colour format before.
In the past, when setting up a colour palette in my root CSS variables, I would have something like this:
:root {
---red: #FA8072;
---redDark: #800000;
--theme: var(--red)
--themeDark: var(--redDark);
}
It worked alright, but it got messy when having multiple shades of multiple colours.
HSL stands for Hue, Saturation and Lightness. It looks like this:
.element {
color: hsl(30, 90%, 50%)
}
Hue 30, Saturation 90%, Lightness 50%.
Using different hues but with the same saturation and lightness results in a palette of colours that share similar tones whilst being different colours.
:root {
// hues
--red: 0;
--yellow: 60;
--green: 139;
--blue: 240;
--purple: 270;
--pink: 300;
// consistent saturation
--palette-saturation: 100%;
// consistent lightness
--palette-lightness: 75%;
// dynamic theme
--theme: var(--red);
}
In use, that would look something like:
.blob--red {
background-color: hsl(var(--red), var(--palette-saturation), var(--palette-lightness))
}
Widgets
The widgets on the homepage were easily the most fun part of the build. I didn't want my site to be too posh or 'medical', I wanted it to feel fun and light - reflecting my personality a bit. We can't all be so serious all the time! I wanted users to let their hair down. Free open bar.
Colour Picker
Nothing too crazy about the colour theme options. I save the user's preferences to their browser's local storage so that when they return, the colour they chose is selected by default.
Dark Mode
I liked that YouTube automatically applies a dark theme if the preference is applied on my computer. I have my OS preference to be light or dark depending on day or night, so if I had spent the whole day on YouTube, which isn't uncommon, I would see it change to dark automatically. Cool! I replicated this with an event listener.
The default setting on the dark mode is "Auto", I did this because it resembles a setting on a camera and it feels nice switching between manual and auto. It was fun to make. This also saves to local storage.
Date and Time
The date and time come from my (London, Europe) timezone; utilising the built-in Date()
javascript object. I catered to several edge cases dependent on the time of day:
- morning coffee time
- lunchtime, brb!
- a regular, 12-hour version (for dweebs)
- being asleep from 11 until 7 (!)
Weather
I used the open weather map api to fetch the weather, from my hometown co-ordinates, and display different messages:
- It's a miracle if it's not overcast in England
- Drizzle is neither here nor there. Either do it or don't! π‘π
The API only returned the temperature in Celcius so I converted and displayed it to Fahrenheit for our fellow Americans. The formulae, which I found on google, is this:
(celcius * 1.8) + 32
This, of course, doesn't solve the bigger issue; that Fahrenheit is a silly form of measurement for temperature and should be abolished forever.
Animal Crossing
What? Something funny about having an Animal Crossing Villager widget on my portfolio?
I used this animal crossing api (the work from a god) to fetch all animal crossing villagers at build time (over 400). Before passing any villagers to the component, I check if it's any villager's birthday and prioritise them - it is their special day, after all! If it's not anyone's birthday, I pass in one random villager. On request, one new villager is randomly fetched.
If for any reason there is a problem with fetching the villagers at build time, I fall back to my favourite villager, "Beau", because he is the cutest villager of them all. I really hope the other villagers don't read this. Please don't tell them.
Spotify
I used using Spotify's developer API to fetch the music I was currently listening to. If I was not listening to anything, I would return the track I last played.
I utilised Next's inbuilt API routes, so I can make the get request in node JS and keep my private keys safe. Using variables from my .env
, on the client-side, meant secret keys were visible when viewing the request in the browser dev-tools. Sure, the average user wouldn't dig for it, but it's a best practice to follow. Danger lurks around every corner!
Strava
I'm using Strava's developer API to fetch my most recent activity, using the same API route technique above. I show the date of the last activity and feel happy when it says "Today" instead of the date from the last one. If it doesn't say "Today", I've been slacking.
Loading & Errors
I have catered, where necessary, for any asynchronous tasks that need to sort themselves out before showing data or none at all.
I could've added a fallback song to the Spotify widget if it failed, so at least something is always there. If I were to do it again, or if I could be arsed rn, I'd do that.
Backend / Blog
I'm using the headless CMS "Strapi" to hold my blog data and authors. I didn't change much after the initial install, I changed the relation between authors and articles so that each can have many (so that I could have multiple authors on a blog).
I then spent about 10 years writing up articles, of terrible quality and published them for all the world to see. Good job, me!
Deployment
Back end
I used Heroku to host the backend / Strapi side of the project.
I had a local testing database on PostgreSQL and then a production database within the Heroku Postgres add-on. That was it really, it just had blogs and authors on there.
Front end
I hosted the frontend on Netlify, my GOD, Netlify is good. I can't believe it is as easy as it is:
I plugged in my git repository for the project, and gave it a build script to run before deploying - yarn build
, it did its thing, and voila.
I added a webhook on the production Heroku (Strapi) application that triggered an auto-deploy on the front-end when I made changes to blogs etc, so I didn't have to manually deploy it each time I made a new post or edited one.
Improvements
As is the case, many times in life; there's always room for improvement! I could sweep these things under the rug, and not be open about it, but I want to be real and show where I went wrong, and where I had to re-work some things to be satisfied.
I ran Lighthouse checks on my site. I thought "I can't be doing much heavy lifting here in terms of performance, nor could I have designed the site SO POORLY it's not even AA Accessible, let alone AAA." The results came back pretty bad. And I mean red-colours-on-the-report bad.
Accessibility
The secondary grey colour I used, when against white, was not passing the WCAG Accessibility guidelines so I had to darken it, nearly to black. It doesn't look as good, but having a green "100" AAA compliant site outweighs that.
Performance
This one was worse! It came back "52" (out of 100). I thought "I should stop being a developer, whilst there's still time." I looked into the data requests I was making - there were very few - and because they seemed very small, I didn't put performance at the front of my mind.
I made some improvements:
- Fetch just 2 individual featured blog posts for the homepage, rather than getting all of them and returning 2 from the array of many.
- Blog data refinement: Only return the large "content" object key value of a blog when on an actual blog page - I didn't need the content when only showing a preview card for a post. The content was also being parsed from mark-down (the Strapi text editor) to an HTML string so I could display it on the front-end, and depending on the length of a post, that was a lot.
- Fonts: I was using the google font CDN to import Inter and Gaegu, nothing crazy. But installing and hosting them locally on the project improved things, as to be expected.
Finished
I'm really happy with how the site came out, it's made some great impressions among friends and colleagues. I hope to continue writing posts and using this blog over the next few years.
If you made it this far, you rock!!