This wasn't a sprint or a grand plan. I browsed my site like a visitor, found the rough spots (CV out of date, blog light on content, slow pages, Lighthouse a11y warnings, weak SEO), and polished them one at a time. It's closer to detailing a car than building one: fix a scuff, step back, spot the next one.
The twist is that I gave an AI assistant the gruntwork and kept the intent and the review for myself.
What actually moved the needle
1) Images, the highest return by far
- Converted PNG and JPEG assets to WebP, with
<picture>fallbacks. - Set explicit width and height to kill layout shift, plus
loading="lazy"anddecoding="async"where it was safe. - Right-sized the oversized art and logos, and kept photos less aggressively compressed to protect quality.
- The payloads got drastically smaller and LCP got quicker, at almost no creative risk.
// Example shape used across components
export function BrandLogo() {
return (
<picture>
<source srcSet="/images/logo.webp" type="image/webp" />
<img
src="/images/logo.png"
width="160"
height="48"
alt="Brand"
loading="lazy"
decoding="async"
/>
</picture>
);
}
2) Loading and CSS
- Removed render-blocking CSS; inlined only critical styles and deferred the rest.
- Tightened preconnect/preload hints to match real assets.
- Swapped "transition-all" for targeted transitions to reduce forced reflows.
3) Content and pipeline hygiene
- Hardened the MDX/blog pipeline so content renders the same locally and in prod.
- Kept CV page and CV PDF in lockstep (a small build step prevents drift).
- Added lightweight checks so broken links/image paths don't slip through.
4) Accessibility and SEO
- Wrote meaningful alt text and clarified the semantics.
- Cleaned up titles, descriptions, and canonicals, and got rid of duplicated headings.
- Small changes, but Lighthouse and the search previews improved fast.
Hardest problem: Mermaid visuals
Front-end visuals were where AI struggled most, especially Mermaid styling, sizing, and theming. What worked:
- Treat diagrams as responsive components (container-width, not fixed px).
- Use Mermaid's built-in theming for dark mode instead of ad-hoc CSS.
- Ensure fonts load predictably so label sizes don't jump between devices.
- Avoid position: absolute hacks; give diagrams a real layout context.
// components/Mermaid.tsx
'use client';
import { useEffect } from 'react';
import mermaid from 'mermaid';
export default function Mermaid({ chart }: { chart: string }) {
useEffect(() => {
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose',
fontFamily: 'ui-sans-serif, system-ui',
});
}, []);
return <div className="mermaid max-w-full overflow-auto">{chart}</div>;
}
My real loop (because it's a personal site)
I ran an extreme programming cycle that I would not use in enterprise apps: prompt → push to main → test in prod. It kept momentum high and the feedback real (because prod is the only truth that matters for a personal site).
Diagram 1 as text
9 steps, 1 of them a decision. Starts at “Spot issue on site”.
Spot issue on site (a starting point)
Write intent + constraints as a prompt
- Go to AI proposes a patch
- Reached from Spot issue on site
AI proposes a patch
- Go to Push to main
- Reached from Write intent + constraints as a prompt
- Reached from Revise prompt with concrete diffs/errors
Push to main
- Go to Prod deploy
- Reached from AI proposes a patch
Prod deploy
- Go to Measure: Lighthouse/UX check
- Reached from Push to main
Measure: Lighthouse/UX check
- Go to Good?
- Reached from Prod deploy
Decision: Good?
- Go to Revise prompt with concrete diffs/errors, when No
- Go to Keep moving, when Yes
- Reached from Measure: Lighthouse/UX check
Revise prompt with concrete diffs/errors
- Go to AI proposes a patch
- Reached from Good?, when No
Keep moving
- Nothing leads out of this step.
- Reached from Good?, when Yes
At work I'd use a safer loop: local run, tests, preview deployments. For a personal site, this speed was fun and productive.
Guardrails that actually helped
- Unit tests as "intent beacons". I add or update tests around the area I'm changing, so the AI has a target and I have a safety net.
- Do-not-touch zones. Config, secrets, and infra are off-limits unless I say otherwise.
- Small diffs with measurable outcomes. If I can't write a one-line "why" in the commit, it's probably too big.
// Example: make CLS improvements explicit
import { render } from '@testing-library/react';
import Home from '@/app/page';
it('renders hero image with fixed dimensions to avoid layout shift', () => {
const { getByAltText } = render(<Home />);
const img = getByAltText(/hero/i) as HTMLImageElement;
expect(img.width).toBeGreaterThan(0);
expect(img.height).toBeGreaterThan(0);
expect(img.getAttribute('loading')).toBe('lazy');
});
The one thing I'd tell someone starting out
If you're stuck in a prompt loop, it's almost always one of two things:
- Context. You haven't given it enough specific code, constraints, or failure output.
- Model. You've given it everything you have, and it's time to try a different one.
Swapping the model broke several deadlocks for me. Once the context is maxed out, a different brain is the only variable left.
Related posts
- Automating my GitHub avatar sync, a small automation with a good return.
- Seasonal avatar borders, runtime flair from simple date logic.
- AI coding tools: when they started earning their keep, on what I hand to automation and why.