For a working designer, a portfolio site sits in an awkward middle ground. It needs to look sharp, load instantly, and stay current without eating into billable hours. My old setup sat on Webflow, which bridged the gap between drag-and-drop builders and professional output better than most tools. But when the renewal notice hit with a £300 annual bill, I had to ask a hard question: was I paying for value, or just convenience?
I decided to rebuild from scratch. The new stack is Astro and Sanity. After living with it for a while, here is exactly what worked, what did not, and where it fits against the tools I used before.
Why Astro for a portfolio?
Most modern web frameworks ship JavaScript first and ask questions later. Astro flips that assumption. It generates plain static HTML at build time and only sends JavaScript to the browser when a specific component actually needs it. They call this islands architecture, but the practical result is simpler: my portfolio pages weigh almost nothing.
Routing is file-based, so creating a new page feels as easy as dropping a file in a folder. Components use a syntax that will feel familiar if you have touched React, Vue, or Svelte. I am not reaching for a new paradigm every time I want to add a project case study.
That said, I would not use Astro to build a complex web application. If you are wiring up authentication, managing global state, or handling real-time data, you will fight the framework. For marketing sites, blogs, and portfolios, though, it stays out of the way. The pages feel fast because they are fast. There is no hydration overhead waiting to render a headline or a paragraph.
Moving from WordPress to Sanity
Before this rebuild, my fallback was always WordPress with Advanced Custom Fields. ACF gives WordPress superpowers, but you are still configuring someone else's house. Sanity works the other way around. You write a schema in code that defines exactly how your content model looks, and Sanity builds the editing interface around your decisions.
I used that control to build a simple page builder out of reusable blocks. I defined a hero section once. I defined a testimonial carousel once. I defined a card grid once. Now I can assemble new pages by stacking those blocks in any order without writing new code or touching a page template.
The difference in mindset matters. With WordPress, I often felt like I was wrestling with a tool that wanted to be a blog. With Sanity, I feel like I am building software. The content becomes clean structured data instead of styled HTML mixed with shortcodes. My project descriptions live as portable objects I could pipe into a mobile app or a newsletter if I wanted to.
A deployment workflow that stays clean
My old WordPress workflow was a mess of FTP uploads, staging subdomains, and plugin updates that always seemed to break at the worst moment. I kept a mental checklist just to publish a typo fix.
The new workflow is short:
- I make changes locally and see them instantly.
- I commit to GitHub when the code feels right.
- Vercel picks up the push and deploys the site automatically.
There is no FTP client. There is no staging database to sync. The repository is the source of truth.
Content works the same way. When I publish or update a post inside Sanity, a webhook tells Vercel to rebuild the site. The static pages regenerate with fresh content, and the CDN updates without me touching a server. Everything stays in sync without manual copying, exporting, or praying that a plugin database migration actually worked.
Tying design and code together with tokens
One of the quieter breakthroughs in this rebuild was setting up a proper token system. I keep a single JSON file that owns every color, type scale, and spacing value in the site. That file is the boss.
I use Token Studio to pull those same values straight into Figma. When my design file says surface-default, it is pointing to the exact same number the code uses. A small script converts the JSON into CSS custom properties at build time, so my stylesheets reference variables like --color-surface-default instead of hardcoded hex codes.
Here is why that matters in practice. If I realize my brand red is slightly too aggressive on mobile screens, I change one value in the JSON file. The Figma library updates. The CSS updates. Every instance across the site updates. I do not need to grep
