Welcome to my blog!
Aug 20, 2023 · Now I just have to actually use it...
It's been a while!
I finally got around to redesigning and redeveloping my website, this time utilizing every trick up my sleeve. Even over just the past few years, web development has changed a mind-boggling amount, but the most significant change to me is the release of Nuxt 3.
I had been using Nuxt 2 for a while, including for my client work, but Nuxt 3 has completely overhauled my workflow for the better. This blog post will serve as a technical overview of the new site and its stack.
The design
My previous website had a very color-forward design, with the primary background color being a bright teal and almost all accents being orange. While teal and orange are still my brand, I wanted to go with a more smooth and minimalistic design this time around, which would also improve accessibility.
The most unique part of my old website was the hexagon particle simulation that enveloped the background, which was built using tsParticles. I love this library, and I wanted to keep this effect, although with a more subtle execution. I decided to integrate it into the background again, but this time colored black with a noise overlay inspired by NuxtLabs's website. The result was an exceptionally dynamic starting point to build my content on top of.
The hero
Speaking of content, my hero section is completely new. Inspired by more minimal Carrd portfolios, I put most of my important information in one centered card that spans the whole viewport on desktop. This also served as a convenient place to put my social media links. To add some flair, I created a "jutting" sub-card with my logo that lies on top of the card on mobile, and on the right third of the card on desktop. I also used this effect for other image-containing cards.
That already looked quite good, but I saw an opportunity. As a nod to my old site, I incorporated another hexagon particle simulation, this time with the bright teal color I had used in my previous website. The effect is almost portal-like, and I absolutely love it.
Adding depth
Some of the primary themes I wanted to explore with this website were depth and layering, especially with transparency. To achieve this, I made the cards slightly transparent - being careful to keep WCAG-compliant contrast on the content! - revealing some of the hexagon background. This transparency was also toyed with to create a more "glass-like" effect on section headings.
Then comes a twist with the navigation: instead of a navigation bar, I decided to use a menu FAB in the top right corner on all screen sizes. Upon being clicked, the entire screen is zoomed out and faded out, with my logo, socials, and a menu of links taking its place with an accompanying transition. This creates the illusion of a "layer" of content that's being zoomed out of the way to reveal the menu.
To further cement this transition of depth, I increased the opacity of the hexagons in the background and zoomed them in when the menu is open. I can only describe this effect as "reaching further into the void," which I'm very proud of.
The tech
Moving on to the tech stack, I decided to build the site with a Jamstack approach using Nuxt 3. I've never been quite a fan of React and Next.js - I find them to be needlessly complex and full of footguns - and Vue has always been my framework of choice. Nuxt 3 is a massive improvement over Nuxt 2, and this website serves as a culmination of my learnings with it.
Modules
My favorite part about Nuxt has always been its modularity. I can pick and choose exactly what I need for my project using Nuxt's built-in modules feature. Not only do I spend less time reinventing the wheel, but I also get to tap into the massive ecosystem of Nuxt modules that other developers have created. This project is using the following modules on top of the Nuxt base:
@nuxt/devtools
for awesome DX@nuxt/content
for rendering the blog's Markdown content@unocss/nuxt
for styling; UnoCSS is a joy to work with@nuxtjs/google-fonts
for loading fonts from Google Fonts (Ubuntu for headings and Poppins for body content)@vueuse/nuxt
for some plug-and-play Vue composables@nuxt/image
for build-time image optimization using the ipx provider@nuxtjs/partytown
for offloading third-party JS to a worker thread@formkit/nuxt
for declarative form-building@nuxtjs/turnstile
for an easy-to-integrate CAPTCHA using Cloudflare Turnstilenuxt-particles
for easy integration with tsParticles (psst, I made this one!)nuxt-simple-robots
for automatic robots.txt generation, which is mostly relevant for...nuxt-simple-sitemap
for automatic sitemap.xml generation (essential for SEO)
Whew... that's a LOT! But it's not as scary as it looks; most of these modules are plug-and-play, and work out of the box without much (if any) configuration. The key here is that I spent way less time configuring third-party libraries and more time building my own content. Most of my work on this front was spent snapping things together like Lego bricks, and all of them mostly barring FormKit have no runtime impact whatsoever.
This also provides for a very interesting workflow. I noticed that I needed a CAPTCHA for my contact form, so I simply
ran pnpm i -D @nuxtjs/turnstile
, added it to nuxt.config.ts
, and that was 90% of the work done. A client-side
<NuxtTurnstile>
component to grab a token in my Vue template was auto-imported, as well as a function to verify
these tokens in a server/API route.
It's this native pluggability that makes Nuxt 3 so powerful and puts a lot of power in the community. In the end, I was able to build this site to completion in only a couple of days during my off-hours.
Wringing out performance
Thankfully, Nuxt has had years of work put into it to make it as performant as possible, and lazy-loading is a first-class feature. I didn't even have to do much to achieve a perfect 98-100 mobile Lighthouse score.
One of the most frustrating pain points of performance optimization is finding out how to integrate third-party scripts without tanking your web vitals. All the code optimizations in the world won't help you if you're loading a huge bundle of JS from Google Analytics, Facebook, and the like and running it on the main thread. That's where Partytown comes in.
Partytown offloads third-party scripts to a worker thread. This takes advantage of the fact that most of these scripts don't need much access to the DOM, and thus can be run in a separate thread, keeping the main thread unpolluted and only running your code. Builder.io has really been releasing wonderful open-source projects such as this, and I'm very grateful for their work.
Image optimization can also be a pain point for load times. @nuxt/image
automatically turns all the site's images into
responsive AVIF/WEBP images at build time, cutting their size down to a couple dozen KB each,
which can be combined with
native lazy loading. All without having to manually resize or compress anything
or using an external service like Cloudinary!
Furthermore, from my testing, the overhead of running two tsParticles simulations at once is surprisingly negligible, and the site has been snappy on every device I've tested it on. If you experience any performance issues, please do let me know! I've been considering adding a switch to disable them (though they're already disabled automatically when the user requests reduced motion due to accessibility concerns).
Backend
The only backend portion of the website is a simple Nuxt API route for the contact form. This performs a check to validate the Turnstile CAPTCHA token, then passes the data on to a Formcake form.
Deployment
Any Jamstack project is most likely to be deployed on one of two platforms: Netlify or Vercel. I've used both in the past, and I've found that I prefer Netlify for various reasons, especially for Nuxt projects.
Perhaps my favorite part about modern Jamstack deployments is that they're free. Netlify's free tier is more than capable of hosting this website, even when running in non-static mode. And the best part? It's 2023: we can deploy on the edge!
Edge deployments allow your site's SSR pipeline to run on Netlify's edge nodes using lightweight Deno Deploy workers. The result is essentially that your JavaScript bundle is hosted on a global CDN, and your site is rendered as close to your users as possible at breakneck speed. This website receives an average response time of 20 milliseconds despite being fully dynamically rendered on the server. Welcome to the future!
In conclusion
This website serves as a culmination of my learnings both with Nuxt 3 and as a web developer in general. I'm extremely proud of the result, and I hope you enjoy it as much as I do. If you have any questions or feedback, or would like to hire me for your next project, please don't hesitate to reach out! I'm always looking for feedback and work. :)