Home/Blog/Redesigning Our Blog
Engineering · 2026

How We Redesigned Our Blog (And Every Bug We Found)

Updated July 2026 · 9 min read · See the result →

Our 5 internship guides used to be a text file with a red button at the bottom. We rebuilt them to actually feel like part of csjobs.ca — and along the way found a routing bug, a caching bug, and a set of company logos that were quietly fake. This is what actually happened, including the parts that didn't work the first time.

5
Posts rebuilt on one stylesheet
3
Real bugs found shipping this
0
New client-side API calls

The Real Problem Wasn't "Ugly"

Our internship guides were fine, technically. Every post had a headline, some paragraphs, a list of companies, and a red "Get matched free" button at the very bottom — after roughly 1,500 words. They loaded fast, they were readable, and nobody had complained about them.

They also didn't feel like they belonged to us. Each of the 5 posts carried its own copy-pasted <style> block — about 90% identical across all five, with small unintentional drift between them. The nav was plain text, no login link, so a returning user landed on a page that only knew how to onboard strangers. And there was exactly one place on the entire page asking anyone to do anything, sitting below every section a reader would have to scroll past first.

None of that shows up in a screenshot of any single post. It shows up the moment you compare the blog to the rest of the site and it feels like a different, older product bolted on the side.

Starting point: 5 static HTML files, each with an inline stylesheet, a text logo, and one CTA at the bottom of the page. No shared design system, no real data, no way for an already-registered visitor to log back in from a blog post.

We Were Inventing Our Own Design System

The first pass fixed the obvious things: one shared blog.css instead of five duplicated ones, a reading-progress bar, a sticky CTA that appears once you scroll past the hero, jump-to navigation, and a second CTA placed where interest actually peaks in each post instead of only at the very end.

It still didn't look like csjobs.ca. Flat red buttons instead of a gradient. Solid navy borders instead of the translucent ones the homepage actually uses. Small rectangular tag chips instead of full pill shapes. A text wordmark in the nav instead of the real logo. Every one of these was a plausible design decision in isolation — and every one of them was something we'd invented instead of something we already had.

The fix wasn't more design. It was less invention. We pulled the actual values out of index.html's stylesheet — --gradient-primary, the real rgba(255,255,255,.08) border color, --radius-lg, the 100px pill radius already used on section tags, the -0.03em letter-spacing on headlines — and used those instead of approximating them from memory. The blog started looking like part of the site the moment it stopped having its own opinions about what "part of the site" should look like.

Three Bugs We Found by Actually Looking

Each of these looked fine in isolation — clean code, no errors thrown, everything technically "working." We only caught them by checking the actual rendered output, not by reasoning about the code.

The routing collision

We added a shared blog.js for the progress bar and sticky CTA, and pointed every post at /blog/blog.js. It 404'd. The reason: /blog/<slug> is a catch-all route that treats anything after /blog/ as a post slug and tries to serve slug + ".html" from disk. A request for /blog/blog.js was quietly trying to serve a file called blog.js.html, which doesn't exist. The fix was moving the script to /style/blog.js instead of special-casing the route — sidestep the collision rather than patch around it.

The cache that outlived our fixes

Partway through, a CSS change we'd just shipped wasn't showing up. Not "eventually" — not at all, for hours, no matter how many times the page was refreshed normally. Fetching blog.css directly from a fresh terminal (bypassing the browser entirely) showed the real problem: production was returning Cache-Control: max-age=14400 — a 4-hour cache lifetime — on a file with no version string in its URL. Every edit we made in that window was invisible to anyone who'd already loaded the page once. The fix: append the file's real mtime as a query string (blog.css?v=1785178903) so every genuine content change produces a new URL, forcing a fresh fetch regardless of what any upstream cache decides to do with the headers.

Want to see the live version of what broke and got fixed?

Our Waterloo internship guide has the real jobs section this post describes
See it live →

The company logos that weren't real

To make the "real jobs" section actually feel real, we pulled each posting's company logo from logo.dev's public API — the same source the homepage's brand marquee already uses. It worked immediately for well-known companies. Then a screenshot showed a solid red square with a plain white "K" sitting next to Google's real four-color logo in the same list.

logo.dev doesn't return an error for a domain it has no real brand asset for — it silently generates a letter-monogram fallback and returns it with a normal 200 OK. Nothing in the response distinguishes "here's the real logo" from "here's a placeholder we made up," so there was no way to detect this from an HTTP status code. We only caught it by downloading the actual PNG and looking at it.

Since the live-jobs section pulls whichever company was scraped that day — not a curated list — there was no way to guarantee every domain had a real indexed logo. The fix was a small allowlist of domains we'd manually confirmed resolve to a real image, built by downloading and eyeballing each one. Anything outside that list shows no logo at all. A missing logo reads as normal. A random placeholder next to a real one reads as broken.

What We'd Tell Anyone Doing This

  1. Reuse your own design tokens before inventing new ones. A page can be well-designed and still look like it belongs to someone else, if every color and radius on it is a plausible guess instead of a value pulled from the system that already exists.
  2. If you want a page to feel alive, show real data — not better copy. No amount of "trusted by hundreds of students" language does what one real, current job listing with a working apply link does.
  3. Third-party APIs fail silently in ways that look like success. A 200 status and a valid image are not the same claim as "this is the real logo." If a result matters visually, look at it — don't just check that the request succeeded.
  4. Unversioned static assets will cost you hours after every deploy. If your CSS or JS URL never changes, some cache between you and your reader eventually decides it doesn't need to check again for a while. Version the URL and the problem disappears.

We put this much care into the blog. The job matching gets more.

Upload your resume and see your AI match score for every open CS internship and new grad role in Canada — free.

Get matched free →
✓ Free forever✓ No credit card✓ 200+ open roles

Related

Like how we build things? See what we're building →
Get matched free