Technology

Basic ingredients

Some notes and opinions on the technology used to build these websites.

Front end

For interactive apps, React—because React hooks are godly. They transformed React from a class-based nightmare into a functional programming paradise. useState for state management, useEffect for side effects, and custom hooks for reusable logic—it’s everything you need without the ceremony of lifecycle methods or the confusion of this binding.

For content and portfolio sites, Astro first. Most of the page is static HTML; React (or any UI library) only shows up as islands where you actually need client interactivity. That split is the point: ship less JavaScript by default, keep the parts that need a framework in React.

Chakra UI because there’s no better collection of cohesive components. Every component follows the same design principles, uses consistent spacing scales, and works seamlessly with TypeScript. The theme system is elegant—you can customize colors, fonts, and spacing globally without hunting through CSS files. Plus, the accessibility is built-in rather than bolted on.

HeroUI (formerly NextUI) sits alongside Chakra for the same reasons: a polished, accessible component system with strong TypeScript support and a design language that stays consistent out of the box. It’s the right default when you want beautiful React UI without reinventing the design system.

Sometimes Tailwind instead for styling because it’s fast and flexible. When you need pixel-perfect custom designs, utility classes beat component libraries. No context switching between HTML and CSS, no naming conflicts, and the build process purges unused classes so your bundle stays small.

Languages

TypeScript for the front end because it’s the perfect mix of static and dynamic typing. You get IntelliSense, refactoring safety, and compile-time error catching without the verbosity of Java or the wild west of plain JavaScript. The gradual typing system means you can start with any and narrow down to specific types as your understanding evolves. It’s amazing how Microsoft made something so good after years of making something so bad. As an aside, the code for VS Code is a delight to read. That is, once you get over the questionable choice of 8-character tabs for indenting code 😱.

Kotlin for backends. I haven’t written Java in years, and I don’t intend to start again now. Kotlin is not just “better than Java”. It’s a completely different language, and it’s a joy to write in.

I’m going to stay with Kotlin for a few minutes, because it deserves wider attention. Kotlin has all the features of modern languages I miss in Java: null safety that actually works (no more NullPointerException surprises), data classes that generate equals(), hashCode(), and toString() automatically, extension functions that let you add methods to existing classes, and coroutines for async programming that don’t require callback hell. Explicit null-checking is a must—the compiler forces you to handle nulls, so runtime crashes become compile-time errors.

The neatest trick is that if the last parameter of a function is a lambda (an inline function), you can put it outside the function parentheses. And if there are no (remaining) parameters, you can omit the parentheses entirely. This may not sound like much, but it’s a big deal. It makes programming in a functional style feel natural rather than syntactic drudgery.

If you wanted—for some unknown reason—to group the display names of available locales by their length, which would you rather write:

// Java
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

public class LocaleUtils {
    public Map<Integer, List<String>> getLocalesByLength() {
        return Arrays.stream(Locale.getAvailableLocales())
                .map(Locale::getDisplayName)
                .collect(Collectors.groupingBy(String::length));
    }
}

or

// Kotlin
import java.util.Locale

fun getLocalesByLength() = Locale.getAvailableLocales()
    .map { it.displayName }
    .groupBy { it.length }

Sold? It’s not just syntactic sugar. It’s objectively vastly superior.

And then there’s C++ for JUCE audio plugins. I use JUCE because it’s the serious toolkit for cross-platform plugins—VST, AU, the whole industry stack—and the DSP and host integration are first-class. What I lament is that you still have to write it in C++. Manual memory, build systems that fight you, and decades of sharp edges, all because real-time audio and plugin hosts demand it. I’d rather be in TypeScript or Kotlin; the audio world has not got that memo. JUCE is excellent. C++ is the tax you pay to ship a plugin that DAWs will actually load.

Frameworks

Spring Boot has auto-configuration, embedded servers, and production-ready features out of the box. Simply add a dependency and get a working application. The opinionated defaults eliminate basic configuration while still allowing customization when needed. Actuator endpoints give you monitoring and health checks for free.

GatsbyJS used to be my default for content sites that needed SEO and fast loading: React, GraphQL, image optimization, and a rich plugin ecosystem. I’m actively replacing it with Astro. Gatsby got heavy—slow builds, GraphQL ceremony for simple markdown, and a dependency graph that aged badly. Astro ships mostly static HTML by default, keeps JavaScript optional, and wins on the metrics that matter for a portfolio or content site: SEO (real HTML in the first response, clean metadata) and speed (smaller bundles, less client JS, faster time-to-interactive). Content collections, file-based routing, and islands of interactivity only where you need them beat Gatsby’s full React tree for this class of site. Several of my older projects still run on Gatsby; they’re moving to Astro as I touch them.

NextJS is a React framework with server-side rendering and static generation. Handles routing, code splitting, and API routes without configuration. The image optimization and internationalization support are production-grade. Perfect for when you need the flexibility of a full-stack framework.

Vite is a lightning-fast build tool that uses native ES modules during development. Hot module replacement that actually works reliably. The build times are measured in milliseconds rather than seconds, which transforms the development experience. For the better.

AWS Lambda is serverless functions that scale automatically and charge only for execution time. Perfect for APIs that have variable traffic or background processing tasks. No server management, automatic scaling, and built-in monitoring through CloudWatch.

Cloud provider

AWS, with a dash of Microsoft Azure.

Like all cloud providers to one degree or another, AWS provides the full ecosystem: S3 for static hosting and file storage, CloudFront for CDN, Route 53 for DNS, DynamoDB for NoSQL heaven, and so on. The service integration is seamless—set up an S3 bucket trigger to process files with Lambda, or use API Gateway to create REST endpoints. The pricing model is predictable for most workloads. Most cloud providers charge per usage, but AWS tends to have the most generous free tier as it is pay-as-you-go for most services.

Azure fills gaps where needed, particularly for enterprise integrations and AD (Active Directory) authentication.

Deployment

I’m migrating hosting from GitHub Pages to AWS CloudFront in front of S3. GitHub Pages is fine for a simple static dump, but CloudFront gives real control over the CDN: cache policies, HTTPS and custom domains, edge behaviour, and invalidation when you need it. More importantly, it lets me consolidate DNS (Route 53), TLS certificates (Certificate Manager), and distribution config into the same AWS CDK stacks that already define the rest of the infrastructure. One place for domain, cert, CDN, and deploy—rather than GitHub Pages for hosting and AWS for everything else.

AWS Amplify remains useful when you want hosting plus full-stack pieces (auth, APIs, databases) in one platform. CloudWatch covers monitoring and logging. Some frameworks still pair tightly with specific hosts—Next.js with Vercel, legacy Gatsby with Netlify—but for my sites the path is static builds on S3, CloudFront at the edge, and CDK for the whole surface.

Infrastructure-as-code

To say I’m not a fan of Terraform is an understatement. I dislike everything from HashiCorp. I generally just have opinions. But this one’s an intransigent position.

Anyway, I use AWS CDK because it has so many advantages over alternatives. First-class language binding is so much more than syntactic sugar. It brings the full power of modern languages with it, including type safety, modularity, and reusability.

It’s great to have everything in one place, and there are some significant advantages hiding in plain sight. For example, the semantic integration of IAM permissions in code is just so powerful. DNS, certificates, CloudFront distributions, and S3 deployments live in the same stacks—which is exactly why the GitHub Pages → CloudFront move is worth the effort.

Think of it like this: If CDK is infrastructure-as-code, then Terraform is merely infrastructure-as-data.

AWS Amplify

I cannot say enough good things about AWS Amplify for website backend integration.

AWS Amplify provides all the things, including authentication, databases, APIs, storage, AI, and hosting in one cohesive platform. You can always write your own Lambda to query Cognito, but why would you want to hurt yourself like that? Under the covers, Amplify uses CDK and AWS AppSync for GraphQL endpoints, API Gateway for REST endpoints, and DynamoDB for databases. It’s a joy to use. So good.