Recent Posts
Grieving with Peter Parker
Saturday, September 7th 2024
Have you ever shed tears over a comic book? I'm not ashamed to admit that I have. For me it was Zeb Wells’ beautifully written issue 11 of Avenging Spider-Man with art by Steve Dillon and Frank Martin, a stunning meditation on overcoming grief that I discovered recently at just the moment when I needed it most. I've been digging into the vast back catalog of Spider-Man comic books lately, and find myself gravitating towards the B-series titles like Avenging that generally feature lower-stakes, one-off stories. I'm the type of reader who values character moments and my own head canon over the byzantine ongoing continuity of the incredibly-long-running main title anyways. Death and grieving are major defining themes of the Spider-Man mythos, from his tragic origin to the many personal losses he's endured throughout his years of superhero-ing, but the ideas are rarely examined with such depth and maturity. The fact that it's snuck into this particular series whose main conceit is getting to watch Spider-Man team up with other Marvel heroes to punch the crap out of various villains-of-the-week makes the issue all the more remarkable. The story in question starts out like any other: Spider-Man happens upon a robbery-in-progress featuring some ridiculously costumed, creature-themed bad guy and swings in to save the day. Except, as the would-be victims and even the villain himself note, New York’s quippiest hero is not acting like himself. He’s in a rush to wrap things up and doesn’t so much as crack a single joke. Something else is on Peter Parker's mind today. We soon learn that Spidey had a good reason to be in a rush: he's en route to an annual visitation at his late Uncle Ben’s grave with the most important lady in his life, his dear old Aunt May. There’s so much to love about this issue. The humor is sweetly understated, with Peter and May gently ribbing each other throughout in between brutally honest admissions of still-lingering guilt and grief. We get to see flashbacks of formative moments in Spider-Man's life, from mourning in the immediate aftermath of his uncle's murder to significant events in his life as a hero that exemplify the importance of his moral code. It's impossible for me not to think about my own mother when reading this issue. She passed away almost a year ago, after living with a terminal cancer diagnosis for the prior three and packing those final years with as much life and love as possible. I see her resilience, generosity of heart, and irrepressible sense of humor reflected in this issue's depiction of Aunt May. Even more than that, I deeply relate with young Peter's feeling of missing a piece of himself with the loss of his Uncle Ben. At the emotional climax of the issue, May offers a heart-wrenching speech about what it feels like to move on from the loss of someone who meant the world to you, whom you relied on for more than you even knew until after they were gone: > __Peter__: Am… am I always going to feel this way? > > __May__: You're wounded, Peter. It's a wound that stays with you. Changes you. You'll see the pain of this world more readily. You know it's there now. You won't be able to ignore it. > > You'll see that life is temporary. Fragile. That every death is as important to someone as Ben's was to you. That every life is worth protecting. > > And you'll feel weak where Ben's strength used to prop you up. But you'll honor him by standing on your own. Because you'll see that Ben taught you how to be strong, and you'll show the world that his strength still sustains you. > > You're going to help people, Peter. I know you are. Because Ben would. > > __Peter__: You won't listen to me. I'm nothing like Uncle Ben. > > __May__: How do you know, Peter? Until now you haven't had to be. I'm a sucker for moments that give dear old May greater character depth than the overly protective and naively oblivious parental figure she’s often portrayed as. This issue has that in droves, giving May full credit for helping Peter internalize the whole “great power/great responsibility” value system (without invoking the famous phrase directly or minimizing Uncle Ben’s role in imparting that lesson in the first place). Another great May moment comes towards the end of the issue with her opaque admission that she is well aware of Peter’s second life as a hero. (This is kept vague so as not to upset the status quo of the main series' continuity, but to this reader it's clear as day what she's saying.) My Mom devoted much of her life to providing care to folks who are often overlooked in today's society. She worked in elder care in various capacities throughout her life, and imbued her work with humor and compassion for individuals who might otherwise feel unseen. She taught me that every person's life has value, to never lose sight of the humanity of others, and that kindness is always worth offering, even (perhaps especially) when there is nothing to be gained in return. And she made absolutely sure that I knew just how proud she was of me and how much love and kindness I had to offer the world. I wouldn’t have expected a comic book to be the piece of art that would trigger the breakthrough I needed to help process my grief. As the story illustrates, mourning and commemorating a loved one is not a process with an end point. It’s something that can and should inform how you go about the rest of your life. Even though she’s no longer with us, Mom’s strength, compassion, and levity are still very much a part of me. Thanks to the wise words of Aunt May (by way of Wells), I’ll always remember to live in a way that would make her proud, making full use of all of the gifts she left me with.Managing technical debt in product-led engineering teams
Wednesday, May 15th 2024
Tech debt is the boogeyman of every fast-paced software team. It's the malicious and secretive force lurking in the corners of your legacy codebase waiting to spring bugs and performance issues onto poor defenseless users when they least expect it, or the necessary evil you pay sacrifice to in service to the ever-demanding roll of forward progress. As an engineer and as a leader, I've often been put in the position of balancing the harsh realities of time constraints and resource pressures squarely in opposition to the critical need to architect technical systems for the ability to scale well and be maintained by other engineers for the foreseeable future. In other words: > "We need this feature and we need it now. Whatever it takes." Wittingly or unwittingly, the decisions of the past (however prudent) will come back to bite you in the tuckus. Before long that rushed-out feature is in front of users and performing "well enough", the engineer who developed it has moved on to something else and forgotten all about it, and the team is left maintaining a system that was haphazardly shipped out with the ever-optimistic line of thinking that "we'll be able to revisit this later". This might come across a little salty, but most of the time this isn't a bad thing. It's how things get done. "Don't let perfect be the enemy of the good" and all that. Engineers who take pride in their work want to deliver high quality software every time, so it can feel crappy to knowingly cut corners. However, having good judgement around when to invest time and resources into optimal system design and clean, well-documented program architecture is one of the things that separates high-performing engineers from the rest. ## Be Realistic Many engineers, once they've felt the pain of maintaining an old system, will attempt to address technical debt before it begins by ensuring that they are always practicing the careful, considered programming style that results in scalable and maintainable software. The problem is that this can be incredibly time-consuming, often unpredictably so, and is in fact rather subjective. By spending more time than necessary on a task, this engineer might end up blocking other stakeholders from being able to verify and deliver on a feature within the expected timeline. And by prematurely fine-tuning their code before soliciting feedback from other engineers, they might also be missing out on insights or strategies from other team members that could influence their approach and save time. Direct communication is absolutely critical. When planning new feature work, be realistic about what's possible and give context to your team on what trade-offs are needed to prioritize expediency over quality. Nobody wants to release subpar work, but an excellent engineer can recognize that the value of delivering an impactful feature sooner and gaining insights and feedback along the way rather than investing an inordinate amount of engineering time for diminishing returns. ## Be Strategic So you've knowingly shipped a feature with tech debt, or (better yet) inherited some by taking on ownership of a pre-existing codebase. Now what? You've probably got a feature roadmap a mile long, a small scrappy team, and high expectations from leadership to deliver meaningful work with measurable improvements on a regular basis. How can you possibly address the growing pile of tech debt under those conditions? One essential strategy is to document the technical debt in your team's project management system, so that the work is clearly visible to everyone as an area that requires further investment. Without this actual artifact representing the work, it really only exists in your brain and can easily be brushed aside (let alone actually prioritized). Once you've written out some details about what should be improved, why the initial decisions were made and some ideas about how to mitigate the trade-offs, it becomes something that can be discussed and assessed and revisited with at least some prior context intact. ## Be Tireless Good engineering leaders will listen to their team when they hear about pain points in the codebase that require additional work. They'll pay attention if developers on the team are communicating to them consistently about the impact of some technical debt. It then becomes the challenge of the engineering lead to synthesize this information, propose specific initiatives to perform the necessary work, and get buy-in from stakeholders to fit it into planned work. This often comes in the form of designated "Flex Time" or some sort of rotating assignment where engineers are specifically asked to spend some amount of their time on engineering-driven initiatives at their own discretion. It might feel like complaining to constantly bring up pain points and areas of tech debt that are already well documented, but each time you do you're giving your leaders more ammunition to make the case for why this work is important and what the benefits of carving out time for it would be. ## Finally... One last bit of advice: celebrate your wins in the battle against tech debt! Treat refactors and upgrades with the same level of respect you give to great new features and innovative technical feats. Nobody will be more excited to revel in these victories with you than your fellow engineers, because they feel the benefits of work like this in their everyday experience. Tech debt is a constant trade-off within software engineering, and even the most carefully-designed systems will incur this cost over time. Improving the efficiency and effectiveness of your engineering team, or fixing a set of looming issues with the product itself before they become a problem, can be just as valuable as delivering net-new user-facing functionality. Learning to communicate the value of that work effectively and advocate for addressing technical debt is an excellent way for engineers to take some additional ownership and control over the work they produce.
Building a custom blog website for free using Next.js, WordPress.com, and GitHub Pages
Wednesday, May 1st 2024
I recently decided to start writing more, so naturally one of my first steps in procrastinating from actually doing so was to figure out how to set up a custom website for my blog in 2024. While I could easily spin up an account on one of the many platforms that exist these days which allow for “no-code” online publishing, where’s the fun in that? I’m a software developer by trade, and if there’s one thing we’re great at it’s taking a simple problem and heavily over-engineering a solution. In all seriousness, there are plenty of reasons why you might want to build your own website from scratch rather than picking something up “off the shelf”. It might be a learning experience, a hobby project, or a means to take complete ownership and control over how your written work is presented to readers online. For myself, as I attempt to document more of my experiences in a new way, it’s a bit of all of the above. ## Platform and Architecture I approached this project with the following goals in mind: - Use a modern frontend tech stack (which, let’s be honest, basically means “React and Typescript” these days), and try to keep it simple - Compose and manage posts in an external, fully featured content management system (I’d rather not host my own CMS or mess with any finicky filesystem-based approaches) - Host the site at little to no cost, while making it easy to push new updates as I develop the site and publish new content ### Why Next.js? There are a few React frameworks out there with great features and community support, but I’ve been partial to Next.js after a few good experiences with it early on. I remember pushing Casper’s tech team to ditch their self-maintained React server-side-rendering solution in favor of Next.js as early as 2017. (We did eventually start shipping much of the site via a Next.js PWA, a transition that led to great improvements in developer productivity and site performance.) Next.js is a robust and flexible framework that prioritizes the developer experience and gives you a lot of options for how you choose to deploy your site. Since I’m aiming to keep costs low for my personal blog, I’ll be using Next.js as a “static site generator” to compile my site pages ahead of time, rather than running a live web server to handle requests on demand. (More on that in a bit.) ### Why WordPress.com? I’ll be using a CMS in a “headless” capacity, since I want to manage my content there and build my own frontend site. For convenience and cost reasons, I ended up returning to the old standby of the CMS world, WordPress. In a past life, I was primarily a PHP developer working mostly on custom WordPress instances, so I know that the platform is a solid choice with support for just about every feature one could ask for in a CMS. WordPress also happens to have a pretty great REST API built in, which is thankfully available for use from even the lowest-tier (read: free) instances on the fully managed version of the platform available from WordPress.com. That means we can spin up a free account and immediately start using it as the content source for a decoupled frontend just by implementing a couple API requests. Score! (I’ve seen some tutorials online that recommend the WPGraphQL plugin for headless integrations. GraphQL is certainly trendier than REST as an API schema, and it does enable a certain amount of optimization for complex views, but with the trade-off of greater technical complexity that doesn’t pay off for many projects. We’re also not able to install third-party plugins on our free WordPress.com site, so we’ve got no choice but to rely on the REST API built into the core platform.) ### Why GitHub Pages? Many platforms these days offer static hosting for free (among their other services), since static websites require much fewer resources vs. sites operating on “dynamic” web servers. I’ve had a personal webpage hosted on my own domain using GitHub Pages (the free static hosting service built into GitHub) for several years, so it was a natural choice for this project. GitHub Pages a convenient way to deploy simple static HTML websites for project documentation, personal homepages, or anything else. For more complex use-cases, it’s possible to configure a workflow using GitHub Actions so that the static site can still be generated from dynamic data sources by running the workflow on GitHub’s servers. ## Getting Started ### Set up a blog on WordPress.com The first thing to do is sign up for a free blog on WordPress.com. At some point in the new user onboarding flow, you’ll be asked to select a domain for your site. Since we’re not using WordPress as the frontend this doesn’t matter to us, so we can go with the free “[blog].wordpress.com” option and enter any blog name you like. (Take note of the domain that you select, we’ll need to reference it later when we integrate with the WordPress API.) There are a bunch of customization options which mostly affect the built-in frontend that we can skip right past, since we won’t be using it. There’s also a “launch my site” button near the end of the flow that we can conveniently leave un-clicked to keep the WordPress site hidden from the public. I was pleased to see that REST API access is enabled even when the site is in its “not launched” state. ### Create a repository on GitHub If you’re following along and you don’t have one already, you’ll need to sign up for an account on GitHub.com. A free account will be sufficient for our needs here as well. Now we’ll create a repo for our project. The name of your repository will determine the URL for the project’s GitHub Pages site. Unless you configure a custom domain, a repository with the name [username].github.io will automatically deploy its GitHub Pages to your personal github.io subdomain, and other projects under your account will get GitHub Pages sites under a sub-path of that domain (so, for instance, a repository called blog would deploy its GitHub Pages instance to [username].github.io/blog). ### Start a new website project with Next.js Following along with the current documentation for Next.js, our first step is to generate a project in our local development environment using the create-next-app generator. It asks a few questions which I left on the default options, shown below: This will generate a working Next.js-based full stack React web application for us. It’s particularly nice that the generator includes Tailwind, a popular utility class framework which will allow us to build out our interface without writing much custom CSS. ## Integrating with the WordPress REST API We’ll focus on building the two essential views for a blog website: the index view where we list out recent posts, usually on the homepage route; and the individual post detail view, where we can read a full article without distractions. The API endpoint for fetching posts from a WordPress blog is open to unauthenticated requests by default, so we’ll be able to integrate this content without any additional setup on the backend. ### Adding a listing of recent posts to the home page Starting with the main “index” route, we’ll want to fetch and display a list of recent posts from our blog. When using the app router (which you should be if you’re following along), you can make asynchronous data requests directly within your React components, making this a fairly simple task. We’ll remove the boilerplate content from the app/page.tsx file which represents the index view, and replace it with an async component which fetches and displays the recent posts: To break this down further, our Home component is requesting posts from the WordPress REST API, converting the JSON response to a parsed array of objects, and rendering an unordered list element containing the post titles and excerpts. (Note that content intended for display is usually contained under “rendered” sub-properties in the REST API response, and that dangerouslySetInnerHTML is necessary to properly display HTML content such as excerpts and post content from the API.) Also worth pointing out: the request URL is constructed with a template string referencing an environment variable, WORDPRESS_COM_DOMAIN. To get that to work, create a .env.local file and populate it with your blog’s domain so that the component knows where to get the post content from: Since we’re using TypeScript, we’ll also want to document an interface for the WordPress post data retrieved from the API. Create a file called types/index.tsx under your project root and define the type like so: (To keep it short, I’ve documented only the attributes that I’m using at the moment rather than the full schema.) Then, update the Home component to assign the correct type to the API response. We need to indicate that the parsed JSON response will be an array of WordPressPost objects which can be done like this: ### Creating a route for individual post details Next, we’ll create a “detail” route for displaying individual posts. Using Next.js path parameters, we can do this by creating a file at app/posts/[slug]/page.tsx with the following: By adding a slug query to the posts API request, we can look up a single post by its URL slug and display its content on the page. ### Adding links between pages To make the site a bit more useful, let’s add links to allow visitors to navigate between our two views. To start, we can update the index view to link out to our new post detail pages like this: Here we’ve just imported the Link component from Next.js, and used it to make the post titles clickable to navigate to our post detail view. It might also be helpful to allow site visitors to easily navigate back to the homepage. This is commonly done by linking the site title at the top of every page, so we can add a heading with another Link to our app/layout.tsx file: ### Applying visual styles At this point, we have all of the necessary content structure built out, but everything is completely unstyled. Because Tailwind applies a CSS reset, we don’t even have a visual hierarchy between heading elements! Using utility classes for margin, padding, and typography, we can start to create some layout and hierarchy on our pages. For instance, to make the post title stand out on the detail page we might do something like this: mb-4 adds margin below the element, font-serif adjusts the font and text-5xl applies one of the larger text size options from Tailwind. We’ll also need to apply styles to the rendered content coming out of WordPress. This will be coming through as HTML strings, so we can’t apply Tailwind classes to elements directly. Instead, we’ll add a custom CSS class (which I like to call rich-text) to each element containing WordPress-generated HTML. We can the define rules in our globals.css file to apply styling to the content. I’m using Tailwind’s @apply syntax to keep the approach consistent with how we’re styling individual elements. Here’s what I came up with as a basic set of essential styles: After applying the class to our post content wrapper, the detail view route file looks like this: With that, we’ve got a decent-looking site displaying the main content from our WordPress blog! All that’s left now is to set up the workflow for deploying the site to GitHub pages and we’ll have ourselves a shiny new website. ## Deploying to GitHub Pages ### Configure Next.js for static output The first thing we need to do before we can deploy our site is to configure Next.js for static site generation. By default, Next.js produces a Node app that you can run on a server to host your site dynamically. If you want to export the site for a static host, you need to update your next.config.mjs file to turn the feature on: There’s one more thing: because we’re pre-rendering the site as static files, Next.js needs to know at build-time what all of the possible routes are that would have pages under the post detail view. For this, we can add a generateStaticParams function export to the post detail’s page.tsx file. This function requests the posts list and returns an array of path parameters to represent each known post. ### Add a GitHub Actions workflow to build and deploy the site GitHub Actions makes it pretty simple to add a workflow to build and deploy the Next.js static website to GitHub Pages. Once you’ve pushed your code to the remote GitHub repository, you can use the UI to browse for, customize, and add the workflow to your project. Otherwise, you can just add this file at .github/workflows/nextjs.yml: The only significant change I’ve made here from the default workflow file is to add the env block under the build job to pass through our WORDPRESS_COM_DOMAIN value. (You’ll need to add this value to your project settings in GitHub as a variable under the github-pages environment.) Commit and push this workflow action to your repo’s main branch and you should see an action kick off on your repository with the first deploy of your new blog! ## Next Steps If you’re reading this on my blog at https://polhem.us, you’re looking at a slightly more built-out version of this very starter application. For anyone who’d like to take the concept and make it their own, I’ve created a repository on GitHub to share the basic, barebones starter application here. My personal site is also open-source under its own repository here. I hope this has been interesting and informative, it was fun for me to put together and I’m excited to do even more with my new site!