I used to write project summaries that listed every feature and called them wins. That felt empty. Now I force myself to write down one concrete thing that broke and how I fixed it. Here are two recent demos and the single lesson each one left me.

Project overview

ProjectStackLessonLive demo
AnimalSoundsVanilla JS, Vite, GitHub ActionsDeployment is part of the feature — base paths matter on GitHub PagesLive demo
TriangleDemoTypeScript, WebGPU API, WGSLThe pipeline is the program — configure the machine, not the pixelsLive demo

AnimalSounds: the base path bug

I built a simple soundboard. The code worked locally. Then I deployed it to GitHub Pages (GitHub Pages documentation) and every asset 404'd.

The browser was asking for https://bradleymatera.github.io/assets/sound.mp3, but GitHub Pages had hosted it at https://bradleymatera.github.io/AnimalSounds/assets/sound.mp3. I knew about base paths in theory, but I had never had to think about it because Vercel and Netlify usually hide that from you. Vite's base option controls the public path prefix for all assets (Vite documentation: base option).

The fix was in vite.config.js:

import { defineConfig } from 'vite'
export default defineConfig(({ mode }) => ({
base: mode === 'production' ? '/AnimalSounds/' : '/',
build: { outDir: 'dist' },
}))

That is the whole thing. I also made sure the GitHub Actions workflow set NODE_ENV=production so the build picked up the right branch. GitHub Actions (GitHub Actions documentation) runs CI/CD pipelines in isolated runners and can set environment variables per job.

Here is the debugging process I followed for this bug:

  1. Deployed to GitHub Pages — Code worked locally, but every asset returned 404.
  2. Compared URLs — Checked the browser's requested URL vs the actual hosted URL on GitHub Pages.
  3. Identified the base path mismatch — GitHub Pages serves project repos under /repo-name/, not /.
  4. Set base in vite.config.js — Conditionally set the base path for production builds only.
  5. Set NODE_ENV=production in GitHub Actions — Ensured the build picked up the correct branch of the config.
  6. Redeployed and verified — Assets loaded correctly on the live URL.

The lesson I kept: deployment is part of the feature. A demo that only runs locally is not finished.

Execution process checkpoint illustration for this section.

WebGPU Triangle Demo: the pipeline is the program

Drawing a triangle in the 2D canvas API is three lines of code. In WebGPU it took me about 150 lines of boilerplate just to get a red triangle on screen. That is not a complaint. It is the point. WebGPU is a modern graphics API that exposes GPU compute and rendering to the web (WebGPU specification, MDN: WebGPU API).

2D Canvas vs WebGPU comparison

Aspect2D Canvas APIWebGPU API
Lines of code for a triangle~3~150
Abstraction levelHigh-level drawing commandsLow-level GPU pipeline configuration
Shader languageNone (built-in drawing)WGSL (WGSL specification)
State managementImplicit (canvas handles it)Explicit (you configure every stage)
PerformanceFine for simple 2DDesigned for high-performance rendering and compute
Learning curveLowHigh — must understand GPU pipeline model
Browser supportUniversalModern browsers only (Browser compatibility)

WebGPU makes you configure the pipeline explicitly:

  1. Request an adapter from the browser.
  2. Request a device from the adapter.
  3. Configure the canvas context with a texture format.
  4. Create a render pipeline with vertex and fragment shaders.
  5. Encode a command to draw.

The shader language, WGSL, is stricter than GLSL. One wrong type and the GPU validation layer throws an error that looks like a crash. I spent an hour debugging a vec2 passed where a vec4 was expected. It was a single line. WGSL is WebGPU's shader language, designed to be safer and more explicit than GLSL (WGSL specification).

The lesson I kept: in modern graphics programming, you are not drawing shapes. You are configuring a machine that draws shapes. The mental model is pipeline first, pixels second.

Delivery workflow checkpoint illustration for this section.

More detail on what each project actually is

The table above gives the overview, but here is what each project actually involved.

AnimalSounds is a soundboard app. You click an animal name, it plays a sound. That is the entire feature set. The stack is vanilla JavaScript with Vite as the build tool and GitHub Actions for CI. The problem it solved was not the app itself — a soundboard is trivial. The problem was deploying a Vite project to GitHub Pages, which has a non-obvious base path requirement that I had not hit before because I had always deployed to Vercel or Netlify. The repo is at github.com/bradleymatera/AnimalSounds and the live demo is at bradleymatera.github.io/AnimalSounds.

TriangleDemo is a WebGPU rendering demo. It draws a single colored triangle on a canvas. The stack is TypeScript, the WebGPU API, and WGSL (WebGPU Shading Language) for the shaders. The problem it solved was learning the WebGPU pipeline model, which is fundamentally different from the 2D canvas API. Where canvas gives you drawing commands, WebGPU gives you a pipeline you have to configure stage by stage. The repo is at github.com/bradleymatera/TriangleDemo and the live demo is at bradleymatera.github.io/TriangleDemo. Note: WebGPU only works in Chromium-based browsers and Safari Technology Preview as of this writing, so the demo will not render in Firefox.

Specific lessons from each project

The table lists one lesson per project. Here is the fuller version of what each one taught me.

From AnimalSounds — deployment is part of the feature. The specific lesson was about Vite's base option, but the broader lesson is that a demo that only runs locally is not finished. I now treat deployment as a first-class concern, not an afterthought. Before I start a project, I know where it will be hosted and what the build output looks like. That sounds obvious, but it took a 404 error on every asset to make it a habit instead of a theory. The other specific thing I learned: GitHub Actions environment variables need to be set explicitly in the workflow file. Setting NODE_ENV=production in my shell did nothing — the CI runner has its own environment, and the config branch depends on that variable.

From TriangleDemo — the pipeline is the program. The specific lesson was about WGSL type strictness (a vec2 passed where a vec4 was expected caused a validation error that took an hour to track down), but the broader lesson is about mental models. In 2D canvas, you think in terms of drawing. In WebGPU, you think in terms of configuring a machine that draws. You set up the adapter, the device, the canvas context, the render pipeline, the shaders, the command encoder — and only then do you submit a draw command. If any stage is misconfigured, you get nothing or a validation error. The lesson I kept is that in low-level graphics APIs, the setup is the work. The draw call is the easy part.

A lesson that applies to both — write the note while the pain is fresh. I wrote the AnimalSounds base path fix within an hour of solving it. I wrote the WebGPU type mismatch note the same day. When I revisit these projects months later, the notes are more useful than the code, because the notes capture the context I would otherwise lose: what I tried, what failed, and why the fix worked.

Why I write this way

Each project now gets one sentence: what it taught me. AnimalSounds is my reference for Vite base paths on GitHub Pages. TriangleDemo is my reference for WebGPU device initialization. When I hit the same problem again, I do not start from zero. I start from the note.

If you are building a portfolio, you do not need more features. You need more notes about what broke.

References