<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Engineering Manager, Tech Advisor & Author ]]></title><description><![CDATA[Engineering manager and technical advisor to startups and Fortune 500 companies – building teams, advising founders, and occasionally shipping things just to see if they work.]]></description><link>https://vorobyov.me/</link><image><url>https://vorobyov.me/favicon.png</url><title>Engineering Manager, Tech Advisor &amp; Author </title><link>https://vorobyov.me/</link></image><generator>Ghost 5.130</generator><lastBuildDate>Wed, 10 Jun 2026 20:44:18 GMT</lastBuildDate><atom:link href="https://vorobyov.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Snapwire: Engineering Lead]]></title><description><![CDATA[Embedded engineering manager and technical product lead on Snapwire for two and a half years — V2 platform, Stripe Connect, and an image pipeline taken from 70% to 95% tagging accuracy.]]></description><link>https://vorobyov.me/snapwire-engineering-lead/</link><guid isPermaLink="false">6a17d60fb3cca50001efb774</guid><category><![CDATA[Marketplace]]></category><category><![CDATA[Media]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[AI / ML]]></category><category><![CDATA[Consulting]]></category><dc:creator><![CDATA[Dennis Vorobyov]]></dc:creator><pubDate>Thu, 28 May 2026 05:45:36 GMT</pubDate><media:content url="https://vorobyov.me/content/images/2026/05/6a8bdbef6c1fc0e01a05d2a0f076d81abc01d16716234de199671066c19a07dd_600.webp" medium="image"/><content:encoded><![CDATA[<img src="https://vorobyov.me/content/images/2026/05/6a8bdbef6c1fc0e01a05d2a0f076d81abc01d16716234de199671066c19a07dd_600.webp" alt="Snapwire: Engineering Lead"><p>Snapwire was a marketplace connecting brands &#x2014; including names like Dell and Starbucks &#x2014; with on-demand photographers. For two and a half years I was the embedded engineering manager and technical product lead on it, working as part of the client&apos;s own team rather than a vendor at arm&apos;s length.</p><p>That distinction matters, so let me be specific about what &quot;embedded&quot; meant here.</p><h2 id="managing-across-two-companies-and-three-timezones">Managing across two companies and three timezones</h2><p>The team I ran was split across organizations. I managed a group of about ten engineers on our side, based in Europe, alongside Snapwire&apos;s own engineers in California and Toronto. Two different companies, three timezones, one standup. I was in that standup every day, working directly with Snapwire&apos;s CTO and their scrum master &#x2014; a fifteen-year ex-Apple engineer &#x2014; as one integrated team. Not &quot;here&apos;s the spec, ping us when it&apos;s broken.&quot; Daily ownership of the technical reality of the product.</p><p>Day to day I monitored every issue, wrote the technical specifications, tightened QA and automated testing, and personally executed the hardest parts of the system. I vetted hires, took part in re-architecting the platform when the product pivoted, and helped set technical direction alongside the client&apos;s leadership.</p><h2 id="what-i-built">What I built</h2><p><strong>The V2 platform.</strong> I rebuilt the product&apos;s user experience with our designer, and a big part of that job was unglamorous reconciliation: keeping the new designs aligned with the actual state of the API and the existing frontend, so what got designed was what could actually ship. A redesign that ignores the live system is just a mockup. I made sure V2 matched reality and improved on it.</p><p><strong>Payments.</strong> I implemented Stripe Connect &#x2014; marketplace payments, with all the splitting, payout, and edge-case handling that two-sided money movement demands.</p><p><strong>The admin backends.</strong> The internal tooling Snapwire&apos;s team used to run the marketplace.</p><p><strong>A real performance win on the frontend.</strong> The React bundle had grown to tens of megabytes. Working with the engineering team, we cut it to around three &#x2014; roughly a ninety percent reduction &#x2014; which, combined with the V2 redesign, transformed load time and the overall feel of the product.</p><h2 id="the-image-pipeline-and-the-part-im-proudest-of">The image pipeline, and the part I&apos;m proudest of</h2><p>Snapwire moved millions of images and videos, so I researched and implemented the CDN pipeline on AWS to serve them at scale. Tagging and quality scoring ran through AWS Rekognition.</p><p>Here&apos;s the honest version: Rekognition out of the box landed around 60&#x2013;70% accuracy on our content. Good enough for a demo, not good enough for a product brands were paying for. So we didn&apos;t stop there. We wrote custom AI/ML code to post-filter Rekognition&apos;s output, and that took tagging accuracy past 95%. The off-the-shelf service was the starting point, not the answer &#x2014; the accuracy that actually mattered came from the layer we built on top of it.</p><h2 id="the-handoff">The handoff</h2><p>Snapwire was acquired by StudioNow. We supported the technical due diligence, helped transition the platform, and &#x2014; the part I care about &#x2014; kept shipping features and polish through and after the acquisition, staying with the product until we genuinely weren&apos;t needed anymore. A smooth handoff isn&apos;t an accident. It&apos;s what happens when the people who built the system are still around, the code is understood, and nobody&apos;s scrambling to reverse-engineer their own product during diligence.</p><h2 id="what-this-kind-of-work-is">What this kind of work is</h2><p>Snapwire is the clearest example of a mode I work in often: not a vendor you brief and wait on, but an engineering manager and technical product lead who embeds in your team, owns the hardest parts of the build, and runs people across companies and timezones as if it were one org. The output isn&apos;t a deliverable handed over a wall. It&apos;s a product I helped run, day by day, until it had a good home.</p>]]></content:encoded></item><item><title><![CDATA[Building HeyTutor]]></title><description><![CDATA[Owning the architecture, infrastructure, hiring, and roadmap of a tutoring marketplace from 2016 to 2025 — and handing it off clean.]]></description><link>https://vorobyov.me/building-heytutor/</link><guid isPermaLink="false">6a17bc05a1b79d00010b1f6a</guid><category><![CDATA[Marketplace]]></category><category><![CDATA[B2B]]></category><category><![CDATA[Consulting]]></category><dc:creator><![CDATA[Dennis Vorobyov]]></dc:creator><pubDate>Thu, 28 May 2026 03:55:55 GMT</pubDate><media:content url="https://vorobyov.me/content/images/2026/05/66d08b53755d903f1ec79853_66aa3056fc896b5b0f44e3f1_649accbbb670ffc2e5aaa2e9_Frame-25201000004575.webp" medium="image"/><content:encoded><![CDATA[<img src="https://vorobyov.me/content/images/2026/05/66d08b53755d903f1ec79853_66aa3056fc896b5b0f44e3f1_649accbbb670ffc2e5aaa2e9_Frame-25201000004575.webp" alt="Building HeyTutor"><p>In 2016, <a href="https://www.forbes.com/profile/heytutor/?ref=vorobyov.me" rel="noreferrer">two founders</a> showed up with a product spec written on a single sheet of A4. They were non-technical, sharp, and they wanted to build a two-sided tutoring marketplace across the United States. They didn&apos;t have a CTO. For the next nine years, I was it &#x2014; without the title.</p><p>That&apos;s the role I want to talk about, because it&apos;s the one most companies don&apos;t know how to ask for.</p><h2 id="the-cto-nobody-hired">The CTO nobody hired</h2><p>HeyTutor never had a CTO on the org chart until 2023. They didn&apos;t need one, because they had me. I owned the architecture, the infrastructure, the CI/CD, the database, the server management, the API selection, the hiring, and the technical roadmap. Operational ownership, not advice from the sidelines.</p><p>People hear &quot;fractional CTO&quot; or &quot;technical advisor&quot; and picture someone who drops in for a strategy call and disappears. That&apos;s not what this was. I was scaling load balancers late at night when traffic spiked. I was in the schema design, the query optimization, the migration planning. The database decisions I made in year one were still holding up the platform in year nine &#x2014; which is the only real test of whether you got them right.</p><h2 id="what-we-actually-built">What we actually built</h2><p>We turned that one-page spec into forty pages, brought in a senior designer for a full InVision prototype, and built V1 from zero. The stack was Vue on the front end &#x2014; an early bet in 2016, when Vue was still proving itself &#x2014; and Laravel with PostgreSQL behind a RESTful API. Stripe for payments. Real-time messaging. CI/CD with proper QA and staging environments from day one.</p><p>Marketplaces are one of the hardest things to build well, because every page has to serve two sides at once. The piece I&apos;m proudest of is the matching engine. Over a hundred signals fed into pairing a student with a tutor: subject and grade level, teaching style, availability windows, timezone compatibility, location proximity, price range, ratings, background-check status, response-time patterns. Get the weighting wrong and the marketplace feels broken even when every individual feature works.</p><p>Timezone-aware scheduling was its own quiet monster. A student in New York and a tutor in Los Angeles need a slot that&apos;s correct in both their calendars, with recurring sessions, cancellations, and two people occasionally trying to book the same slot at the same instant. None of that is glamorous. All of it has to be exactly right.</p><p>We also generated thousands of local SEO landing pages &#x2014; &quot;math tutors in Los Angeles,&quot; &quot;SAT prep near me&quot; &#x2014; programmatically, each with location-specific content. They became one of the platform&apos;s primary organic acquisition channels.</p><h2 id="the-pivot-that-changed-the-company">The pivot that changed the company</h2><p>HeyTutor started B2C. Then school districts came calling, and B2B turned out to be a different animal entirely. Districts don&apos;t browse a marketplace and pick tutors one by one &#x2014; they bulk-hire and assign through a CRM-driven workflow, and they need to plug into the e-learning infrastructure they already run. So we built a separate B2B layer on top of the existing platform: Google Classroom integration, district-level dashboards, bulk assignment workflows, and the compliance and reporting that government contracts demand.</p><p>That layer is what let HeyTutor win LAUSD &#x2014; the largest school district in the United States. Contracts at that scale don&apos;t tolerate a platform that&apos;s flaky or undocumented. The engineering has to be right, and it has to be provably right.</p><h2 id="onsite-because-some-things-dont-happen-over-slack">Onsite, because some things don&apos;t happen over Slack</h2><p>Roadmaps that actually stick get built in a room. I flew our technical lead and PMs out to California to sit with the HeyTutor team in person, set the direction, and decide together what to build, what to defer, and what to kill. You can run a nine-year engagement remotely, but the inflection points &#x2014; the moments where the product&apos;s direction changes &#x2014; are worth being in the same room for.</p><h2 id="how-i-measure-a-good-engagement-the-handoff">How I measure a good engagement: the handoff</h2><p>In 2023, HeyTutor hired their first in-house CTO, and I handed the technical leadership over to their internal team. That&apos;s the outcome a fractional engagement should produce. The company grows until it needs and can afford a full-time technical leader, and the handoff is clean because the codebase, the infrastructure, and the processes are all documented and well-maintained. A messy handoff means you built a dependency, not a system. A clean one means you did the job.</p><p>By the end: 10,000+ tutors, 10,000+ students, tens of thousands of sessions a month, continental US coverage, multiple funding rounds, and both founders on the Forbes 30 Under 30 list. Nine years &#x2014; my longest engagement, and the one I&apos;d take again without hesitating.</p><h2 id="what-this-kind-of-work-is">What this kind of work is</h2><p>If you&apos;re a non-technical founder with a hard product and no senior engineering leadership, this is the gap I fill: someone who owns the technical reality of the company end to end, builds the team, makes the architecture decisions you&apos;ll be living with for years, and hands it off clean when you&apos;ve outgrown the arrangement. Not a deck. The actual thing.</p>]]></content:encoded></item><item><title><![CDATA[Sanity CMS in 2026: The Headless CMS That Actually Respects Your Time]]></title><description><![CDATA[Sanity gives you 20 seats and 10K docs free – this is a free tier which would suit the most SMBs and startups, mid to long term.]]></description><link>https://vorobyov.me/sanity-cms-in-2026-the-headless-cms-that-actually-respects-your-time/</link><guid isPermaLink="false">6a057983a1b79d00010b1f53</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Dennis Vorobyov]]></dc:creator><pubDate>Thu, 14 May 2026 07:29:20 GMT</pubDate><media:content url="https://vorobyov.me/content/images/2026/05/c57153e5c666da65c6a5b65c26be71df9f5d029e-1200x630.png" medium="image"/><content:encoded><![CDATA[<img src="https://vorobyov.me/content/images/2026/05/c57153e5c666da65c6a5b65c26be71df9f5d029e-1200x630.png" alt="Sanity CMS in 2026: The Headless CMS That Actually Respects Your Time"><p>I used Contentful for years. It was fine. The content model was decent, the API was fast, the editorial experience was good enough. Then on April 30, 2025, Contentful decided that &quot;free&quot; meant 25 content types, 100K API calls, and a $300/month cliff if you go over.</p><p>We had 30+ content types. The math was simple. We left.</p><h2 id="why-sanity">Why Sanity</h2><p>I evaluated Strapi, Payload, Storyblok, and Sanity. Here&apos;s the honest version of that evaluation:</p><p><strong>Strapi</strong> is open source and self-hosted. Great for data sovereignty. Terrible if you don&apos;t want to maintain a Node.js server, a Postgres database, and all the DevOps that comes with it. I run a software studio. I have engineers. I still didn&apos;t want to babysit a CMS server.</p><p><strong>Payload</strong> is fascinating &#x2014; a CMS that lives inside your Next.js app. One deployment unit. TypeScript-native. 42K GitHub stars. If we were on Next.js, I&apos;d seriously consider it. We&apos;re on Astro. Moving on.</p><p><strong>Storyblok</strong> has the best visual editing for marketers. Drag-and-drop, WYSIWYG, the whole thing. But our marketers are me, and I type Markdown in a text editor. Visual page builders solve a problem I don&apos;t have.</p><p><strong>Sanity</strong> gave me 20 seats, 10,000 documents, 1 million CDN requests, and 100GB bandwidth. Free. The Studio is React-based &#x2014; same stack our team uses for client work. The content model is defined in code, not clicked together in a UI. And the query language (GROQ) is genuinely better than GraphQL for content.</p><p>That&apos;s it. That&apos;s the evaluation. Sometimes the right answer is the one that costs nothing and works immediately.</p><h2 id="what-sanity-actually-is">What Sanity actually is</h2><p>Sanity is a headless CMS with a twist: the editing interface (Sanity Studio) is a React app that you own, customize, and deploy. The content lives in Sanity&apos;s cloud (the &quot;Content Lake&quot;). You query it with GROQ or GraphQL. You render it with whatever frontend you want.</p><p>The Studio runs at <code>your-project.sanity.studio</code> or on your own domain. You define schemas in TypeScript. You build custom input components in React. You can replace literally any part of the editing interface with your own code. Most CMSes give you a settings panel. Sanity gives you a React app.</p><p>This means the setup cost is higher than WordPress. You&apos;ll spend developer time configuring the Studio. But the ceiling is also higher &#x2014; dramatically higher. Our Studio has custom preview components for blog posts, structured case study schemas with stack arrays and metric objects, and an author reference system. Took a few hours to set up. Works exactly how we need it to.</p><h2 id="groq-is-weird-groq-is-also-better">GROQ is weird. GROQ is also better.</h2><p>GROQ (Graph-Relational Object Queries) is Sanity&apos;s query language. It looks like nothing you&apos;ve seen before. The first time you see <code>*[_type == &quot;post&quot;]{title, &quot;author&quot;: author-&gt;name}</code> you think &quot;what is this syntax.&quot; The second time you think &quot;wait, that just resolved a reference in one character.&quot;</p><p>The <code>-&gt;</code> operator dereferences a reference. In GraphQL, you&apos;d need a resolver, a type definition, and a separate query. In GROQ, you add two characters.</p><p>Where GROQ wins over GraphQL: server-side projections (you shape the exact JSON your component needs in the query), no deployment step (schema changes are queryable instantly), real-time subscriptions work natively, and reference following is trivial.</p><p>Where GraphQL still wins: schema introspection, cross-source federation, and the fact that every developer on earth already knows it. GROQ has a learning curve. Not a steep one &#x2014; maybe two hours to be productive, a week to be fluent &#x2014; but it&apos;s one more thing.</p><p>Sanity supports both. If your team has strong GraphQL opinions, use GraphQL. You&apos;ll give up real-time subscriptions and mutations (Sanity&apos;s GraphQL layer is read-only), but you&apos;ll get the tooling you already know.</p><h2 id="portable-text-why-it-matters-more-than-you-think">Portable Text: why it matters more than you think</h2><p>Most CMSes store rich text as HTML. Sanity stores it as a JSON block array called Portable Text.</p><p>&quot;Who cares?&quot; You will, the first time you need to render the same content in a React web app, a React Native mobile app, an email template, and an LLM context window. HTML is designed for browsers. Portable Text is designed for anywhere.</p><p>Every rendering layer has a Portable Text renderer: React, Vue, Svelte, native iOS, native Android. You write custom serializers for your own block types. An inline &quot;callout&quot; block in the editor becomes a styled callout in the web app, a plain text block in the email, and structured metadata for the AI agent. Same source content, four different outputs, zero HTML parsing.</p><p>The trade-off: pasting from Google Docs into the editor doesn&apos;t always do what you expect. Block-level formatting survives. Inline formatting sometimes doesn&apos;t. Your editors need 15 minutes of training. After that, it&apos;s fine.</p><p>A bigger trade-off we learned the hard way: pasting rich text from chat interfaces (like Claude&apos;s web UI) can silently rewrite relative URLs to absolute ones with the wrong domain. We discovered 146 links pointing to <code>claude.ai</code> instead of <code>eltexsoft.com</code> because Sanity Studio&apos;s rich text editor resolved <code>/blog/...</code> against the page URL. We wrote a script to fix them. It took an hour. But it shouldn&apos;t have happened, and it wouldn&apos;t have if we&apos;d pasted Markdown instead.</p><h2 id="the-mcp-thing-is-actually-a-big-deal">The MCP thing is actually a big deal</h2><p>Sanity has a Model Context Protocol server. MCP is the protocol that lets AI agents (Claude, Cursor, VS Code agents, etc.) interact with external tools. Sanity&apos;s MCP server lets an AI agent query your content, create documents, patch fields, manage releases, and run semantic search &#x2014; all through natural language.</p><p>I use this daily. &quot;Query all posts where the slug contains &apos;outsourcing&apos;.&quot; &quot;Patch the seoTitle on document ID 93299147 to this value.&quot; &quot;Publish these 13 documents.&quot; Claude does it. I verify. Done.</p><p>We created blog posts, patched SEO fields across 31 pages, fixed 146 broken links, and published 36 articles through a combination of MCP operations and Studio editing. The MCP connection turns content management from a point-and-click operation into a programmable one.</p><p>No other headless CMS has anything close to this in May 2026. Contentful doesn&apos;t. Strapi doesn&apos;t. Payload doesn&apos;t. Sanity&apos;s MCP server is the single biggest competitive advantage in the headless CMS market right now, and almost nobody is talking about it because &quot;CMS has an AI integration&quot; sounds like marketing fluff. It&apos;s not fluff. It&apos;s the difference between manually editing 31 meta descriptions and telling Claude to do it in one prompt.</p><h2 id="real-time-collaboration">Real-time collaboration</h2><p>Multiple editors on the same document. Google-Docs-style cursors. Keystroke-level presence. Conflict-free because the Content Lake is patch-based &#x2014; every change is a discrete operation, not a full document overwrite.</p><p>Most headless CMSes have &quot;collaboration&quot; that means &quot;optimistic locking&quot; &#x2014; if two people edit the same document, one of them loses. Sanity&apos;s collaboration means two people type in the same field at the same time and both changes survive. Contentful would need a near-total architectural rewrite to match this.</p><p>I&apos;m the only editor on our site, so I don&apos;t use this feature much. But for agencies managing content for clients, or companies with editorial teams, this is the difference between &quot;don&apos;t touch that document, I&apos;m editing it&quot; and &quot;go ahead, we&apos;re both editing it.&quot;</p><h2 id="the-money">The money</h2><p><strong>Sanity Free:</strong> 20 seats, 10,000 documents, 1M CDN requests, 250K API requests, 100GB bandwidth, 100GB assets. Real-time collaboration, Media Library, GROQ + GraphQL.</p><p><strong>Sanity Growth:</strong> $15/seat/month (viewers free). 25,000 documents, 5 roles, AI Assist, Comments, Tasks, Scheduled Drafts, Content Releases.</p><p><strong>Contentful Free (post-April 2025):</strong> 25 content types, 100K API calls, 50GB bandwidth. No grandfathering. Exceeding limits can result in suspension.</p><p><strong>Contentful Paid:</strong> Somewhere between $300 and $850/month depending on which source you trust (PricingSaaS, Vendr, and CostBench all report different numbers). Get a sales quote. Don&apos;t trust the internet, including me.</p><p>For a studio our size, Sanity&apos;s free tier covers everything. 36 blog posts, 13 case studies, 10 industry pages, 9 tech pages, 22 course chapters, authors, metadata &#x2014; all well under 10,000 documents. We&apos;ll hit Growth when we need Content Releases or Tasks. That&apos;s $15/month for one editor seat. Not $300.</p><h2 id="where-sanity-falls-apart">Where Sanity falls apart</h2><p><strong>The Studio needs configuration.</strong> A fresh install is functional but bare. You&apos;ll spend developer days making it feel good for editors. Schema definitions, custom previews, desk structure, validation rules &#x2014; it&apos;s all code you write. This is simultaneously Sanity&apos;s greatest strength and its highest barrier to entry.</p><p><strong>Document limits are real.</strong> 10K on Free, 25K on Growth. Reference-heavy schemas inflate document counts because references create separate documents. If you have 5,000 blog posts with 3 author references each, those references count. The Increased Quota add-on is $299/month. That&apos;s a cliff.</p><p><strong>API vs CDN billing will bite you.</strong> Direct API requests to <code>api.sanity.io</code> cost 10x more per unit than CDN requests to <code>apicdn.sanity.io</code>. A misconfigured <code>@sanity/client</code> with <code>useCdn: false</code> will inflate your bill for no reason. Always set <code>useCdn: true</code> for read operations.</p><p><strong>No self-hosting.</strong> The Content Lake is Sanity&apos;s cloud. Period. You can export everything (GROQ + export CLI), so migration is feasible, but you&apos;re a SaaS customer. If &quot;I own the server&quot; is a hard requirement, use Strapi or Payload.</p><p><strong>No native visual page builder.</strong> If your primary editor wants to drag-and-drop sections like Squarespace, Sanity is the wrong tool. It has Visual Editing (click-to-edit overlays on your frontend) and the Presentation tool, but it&apos;s not a page builder. It&apos;s a structured content editor. Different animal.</p><p><strong>GROQ is one more thing.</strong> Your team learns React. TypeScript. Your framework. Your deployment platform. GROQ. That&apos;s a real cost. It pays off, but don&apos;t pretend there&apos;s no ramp.</p><h2 id="the-bottom-line">The bottom line</h2><p>Sanity is a CMS for people who think about content as structured data, not as pages. If that sentence resonates with you, Sanity is probably the right choice. If that sentence sounds like unnecessary complexity, Storyblok or WordPress might serve you better. No judgment.</p><p>We migrated from Contentful using the official <code>contentful-to-sanity</code> CLI. One command. The content came over clean. The Studio took a day to configure. The MCP integration took 10 minutes. The blog has been running for two weeks with zero issues.</p><p>30,000+ organizations use Sanity. Nike, Sonos, Figma, Linear, National Geographic, Shopify, Spotify. $85M Series C. $40M+ ARR. This isn&apos;t a bet on an underdog. This is a bet on the CMS that quietly became the default while everyone was arguing about WordPress vs Contentful.</p><p>The free tier is generous. The content model is flexible. The AI integration is years ahead. And the query language is weird in the way that good tools are always weird at first &#x2014; it feels wrong until you realize it&apos;s right, and then you can&apos;t go back.</p><hr><p><em>We rebuilt </em><a href="https://eltexsoft.com/?ref=vorobyov.me"><em>eltexsoft.com</em></a><em> on Astro + Sanity. The full teardown: </em><a href="https://vorobyov.me/rebuilding-agency-website-in-2026-lessons-learned/" rel="noreferrer"><em>Rebuilding Our Agency Website in 2026</em></a><em>. The Astro half of the story: </em><a href="https://vorobyov.me/astro-in-2026-the-framework-that-wins-by-doing-less/" rel="noreferrer"><em>Astro in 2026</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[Astro in 2026: The Framework That Wins by Doing Less]]></title><description><![CDATA[Astro ships zero JS by default, topped State of JS 2025 at 94% satisfaction, and got acquired by Cloudflare. Here's why it matters.]]></description><link>https://vorobyov.me/astro-in-2026-the-framework-that-wins-by-doing-less/</link><guid isPermaLink="false">6a05788fa1b79d00010b1f43</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Dennis Vorobyov]]></dc:creator><pubDate>Thu, 14 May 2026 07:24:55 GMT</pubDate><media:content url="https://vorobyov.me/content/images/2026/05/og-astro-5.BgCoDjK6.webp" medium="image"/><content:encoded><![CDATA[<img src="https://vorobyov.me/content/images/2026/05/og-astro-5.BgCoDjK6.webp" alt="Astro in 2026: The Framework That Wins by Doing Less"><p>I spent 10 years building websites with frameworks that ship 200KB of JavaScript to render a paragraph of text. Vue, Nuxt, React, Next &#x2014; all brilliant tools for building apps. All absurdly overpowered for a marketing site.</p><p>Then I found Astro. And I felt like an idiot for not finding it sooner.</p><h2 id="what-astro-actually-is">What Astro actually is</h2><p>Astro is a web framework that ships zero JavaScript by default. You write components &#x2014; in Astro&apos;s own syntax, or in React, Vue, Svelte, literally whatever you want &#x2014; and the build step renders them to plain HTML. The JavaScript disappears. What lands in the browser is HTML and CSS. That&apos;s it.</p><p>A typical page on our site is 5-15KB. Not 5-15KB gzipped. 5-15KB total. The Lighthouse score is 95+ on mobile without trying. Not because we optimized aggressively &#x2014; because there&apos;s nothing to optimize. You can&apos;t have a slow JavaScript bundle if there is no JavaScript bundle.</p><p>&quot;But what about interactivity?&quot; Fine. You mark a component with <code>client:load</code> or <code>client:visible</code> and it hydrates independently. One interactive widget on a page of 20 components means one component ships JS. The other 19 are static HTML. Astro calls this &quot;Islands Architecture.&quot; I call it &quot;not shipping code nobody asked for.&quot;</p><h2 id="the-numbers-that-made-me-switch">The numbers that made me switch</h2><p>State of JavaScript 2025 surveyed thousands of developers. Astro hit 94% positive sentiment. Number one. SvelteKit came in at 88%. Nuxt at 80%.</p><p>Next.js? 55%. Down from 68% the year before.</p><p>The survey editors didn&apos;t sugarcoat it: Next.js keeps gaining market share while losing satisfaction at the same time, and now has a 39-point gap with Astro. That&apos;s a framework running on inertia, not love.</p><p>I don&apos;t make technology decisions based on popularity contests. But when 94% of people who use a tool like it, and 45% of people who use the alternative don&apos;t, the signal is hard to ignore.</p><h2 id="cloudflare-bought-them">Cloudflare bought them</h2><p>January 16, 2026. Cloudflare acquired The Astro Technology Company. All Astro employees became Cloudflare employees. Framework stays MIT-licensed.</p><p>The strategic logic is obvious. Vercel has Next.js. Cloudflare needed a framework story. Astro&apos;s static-first, edge-friendly architecture is a natural fit for Workers, R2, and D1. Expect Cloudflare-specific features to ship faster than competing platform adapters.</p><p>Should you worry? A little. &quot;Stays open source&quot; is a stated commitment, not a contractual guarantee. Governance changes typically lag acquisitions by 12-24 months. But Astro deploys anywhere &#x2014; Vercel, Netlify, Node, Deno, Hetzner with nginx. We deploy on Hetzner. Cloudflare has zero leverage over our hosting choice. That&apos;s the beauty of static HTML.</p><h2 id="astro-60">Astro 6.0</h2><p>Shipped March 10, 2026. The highlights that actually matter:</p><p><strong>Rebuilt dev server on Vite&apos;s Environment API.</strong> Faster HMR. You won&apos;t notice unless your project is large, then you&apos;ll notice a lot.</p><p><strong>First-class Cloudflare Workers runtime.</strong> Unsurprising given the acquisition. If you deploy to Workers, this removes a layer of adapter friction.</p><p><strong>Stable Content Security Policy API.</strong> Astro is the first JavaScript meta-framework with built-in CSP for both static and dynamic pages. If you&apos;ve ever hand-rolled CSP headers in nginx and wanted to cry, this is for you. (I have. I did.)</p><p><strong>Experimental Rust compiler.</strong> Not default yet. Will be. The team is rewriting the compiler in Rust for speed. Early benchmarks show 2x faster rendering. Treat that number as directional &#x2014; vendor benchmarks always are.</p><p><strong>Built-in Fonts API.</strong> Sounds minor. Isn&apos;t. Google Fonts without the privacy headache and layout shift. Self-hosted, optimized, one config line.</p><p>If you&apos;re not ready for v6, run 5.17.x. It&apos;s stable, battle-tested, and what we&apos;re on right now. Upgrade after 6.1 or 6.2 when the integration ecosystem catches up.</p><h2 id="content-collections-are-the-killer-feature-nobody-talks-about">Content Collections are the killer feature nobody talks about</h2><p>This is the feature that sold me. Not the performance. Not the zero JS. The content model.</p><p>You define a schema in Zod. You put Markdown files in a folder. Astro validates every file against the schema at build time. Type errors, missing fields, wrong formats &#x2014; all caught before deploy, not in production at 2 AM.</p><p>We have 116 pages: services, industries, tech stacks, case studies, course chapters. Every content type has a Zod schema. Every markdown file is validated. If an engineer adds a case study with a missing <code>stack</code> field, the build fails. Not &quot;renders wrong.&quot; Fails. Loudly.</p><p>The Content Layer API (stable in 5.0) generalized this further. You can write loaders that pull from anywhere &#x2014; local files, APIs, headless CMS, databases &#x2014; into the same type-safe pipeline. Our blog posts live in Sanity. Our marketing pages live in Markdown. Same build, same types, same validation. Both sources.</p><h2 id="the-ai-crawlability-angle-nobodys-writing-about">The AI crawlability angle nobody&apos;s writing about</h2><p>Here&apos;s the thing that matters in 2026 and almost nobody is talking about.</p><p>GPTBot, ClaudeBot, PerplexityBot, Google&apos;s AI Overviews &#x2014; they fetch your HTML. They don&apos;t execute your JavaScript. If your content lives inside a React component that renders client-side, it doesn&apos;t exist to them.</p><p>Our old Nuxt site was invisible. Zero organic traffic for years. Not because the content was bad. Because the content wasn&apos;t in the HTML.</p><p>Astro&apos;s output is pure HTML. Every word on every page is in the source. AI bots read it. Search engines index it. LLMs can summarize it. We went from zero to 116 fully indexable pages in two weeks. The SEO value of &quot;your content is actually in the HTML&quot; is impossible to overstate in a world where half the web hides behind client-side rendering.</p><h2 id="who-else-is-using-it">Who else is using it</h2><p>IKEA, Unilever, Visa, NBC News, OpenAI, Cloudflare&apos;s own developer docs, Microsoft, Adobe, Porsche, The Guardian, Bloomberg, Deloitte, LEGO. TechnologyChecker tracks 49,000+ active domains on Astro. Migration data shows a 12:1 ratio of companies moving from Gatsby to Astro versus the other direction.</p><p>This isn&apos;t an indie framework anymore.</p><h2 id="where-astro-falls-apart">Where Astro falls apart</h2><p>I&apos;ll be honest because nobody else is.</p><p><strong>Complex stateful apps.</strong> If you&apos;re building a dashboard, a Figma clone, a real-time multiplayer game &#x2014; Astro is the wrong tool. It&apos;s content-first. Everything else is a workaround. Use Next.js, SvelteKit, or whatever your team knows.</p><p><strong>The ecosystem is younger.</strong> Auth, advanced i18n, complex form state &#x2014; you&apos;ll write more glue code than in Next.js. The integrations exist but they&apos;re not as mature.</p><p><strong>Astro DB was a miss.</strong> The hosted database service (Astro Studio) shut down in early 2025. Fred Schott was unusually candid about it: they never found product-market fit. Astro DB still works as a wrapper around libSQL/Turso, but don&apos;t pick Astro for the database story. Pick it for the rendering story.</p><p><strong>Cloudflare alignment.</strong> The framework deploys everywhere today. But Cloudflare-specific features will get priority. If that bothers you philosophically, factor it in.</p><h2 id="the-bottom-line">The bottom line</h2><p>I run a software engineering studio. We build apps for Fortune 500s. Our own website runs on Astro on an $8.60/month Hetzner VPS with nginx. It builds 116 pages in 2.66 seconds. Lighthouse scores are 95+. Ahrefs health score is 99/100. Every AI bot on the internet can read every word.</p><p>The framework won by doing less. Less JavaScript, less complexity, less cleverness. More HTML. More speed. More content that actually shows up when someone (or something) looks for it.</p><p>94% satisfaction isn&apos;t a fluke. It&apos;s what happens when a tool does exactly what it promises and nothing else.</p><hr><p><em>We rebuilt </em><a href="https://eltexsoft.com/?ref=vorobyov.me"><em>eltexsoft.com</em></a><em> on Astro + Sanity. The full teardown: </em><a href="https://vorobyov.me/rebuilding-agency-website-in-2026-lessons-learned/" rel="noreferrer"><em>Rebuilding Our Agency Website in 2026</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[Rebuilding agency website in 2026 – Lessons learned]]></title><description><![CDATA[How we rebuilt eltexsoft.com with Astro, Sanity, and Ahrefs MCP. Zero JS, 99 health score, 116 pages, $8.60/month hosting. The full stack and lessons learned.]]></description><link>https://vorobyov.me/rebuilding-agency-website-in-2026-lessons-learned/</link><guid isPermaLink="false">69b8ee94ee776b00015712e2</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Dennis Vorobyov]]></dc:creator><pubDate>Tue, 17 Mar 2026 06:03:00 GMT</pubDate><media:content url="https://vorobyov.me/content/images/2026/05/Screenshot-2026-05-14-at-02.58.17.png" medium="image"/><content:encoded><![CDATA[<img src="https://vorobyov.me/content/images/2026/05/Screenshot-2026-05-14-at-02.58.17.png" alt="Rebuilding agency website in 2026 &#x2013; Lessons learned"><p>I run a software engineering studio. We&apos;ve built apps for Fortune 500s, scaled marketplaces, shipped iOS apps that Apple featured at WWDC. Our own website was running on Nuxt + Contentful and had zero organic traffic.</p><p>Zero. For years.</p><p>Not because the site was bad. The design was fine. The content was fine. The problem was that Nuxt ships JavaScript, and AI search bots don&apos;t execute JavaScript. GPTBot, ClaudeBot, PerplexityBot &#x2014; they fetch your HTML and move on. If your content lives inside a Vue component that renders client-side, it doesn&apos;t exist to them.</p><p>So we rebuilt the whole thing. Here&apos;s what happened.</p><h2 id="why-astro">Why Astro</h2><p>I looked at Next.js first. Everyone does. But for a marketing site &#x2014; services, case studies, blog &#x2014; Next.js is overkill. You&apos;re shipping 85-180 KB of React runtime to render what is fundamentally static text and images. The App Router adds complexity we&apos;d never use. And the CVE list in 2025 was not confidence-inspiring.</p><p>Astro ships zero JavaScript by default. A typical page is 5-15 KB of HTML and CSS. That&apos;s it. The framework is purpose-built for content-heavy sites: static site generation, Content Collections with Zod validation, built-in sitemap and RSS. State of JS 2025 ranked it #1 by developer satisfaction, 39 points ahead of Next.js.</p><p>Cloudflare acquired the Astro Technology Company in January 2026. Framework stays MIT-licensed. If anything, the backing makes the bet safer.</p><p>The deciding factor was simpler than all of that: AI bots can read our content now. Every word on every page is in the HTML. No hydration, no client-side rendering, no hoping that Googlebot&apos;s renderer feels like executing your JavaScript today.</p><h2 id="why-sanity-and-why-we-left-contentful">Why Sanity (and why we left Contentful)</h2><p>Contentful&apos;s April 2025 free-plan crackdown sealed it. 25 content type cap, 100K API calls. Their Premium tier starts at $300/month. We&apos;re a 35-50 person studio, not a media company. That math doesn&apos;t work.</p><p>Sanity&apos;s free tier gives us 20 seats, 2 datasets, 10K documents, 1M CDN requests. The Studio is React-based &#x2014; same skills our team uses for client work. The official <code>contentful-to-sanity</code> CLI migrated our content in one command. And Sanity&apos;s structured content model is genuinely better for a site with services, case studies, industries, staffing models, and tech skill pages that all cross-reference each other.</p><p>We kept marketing pages (services, industries, tech stacks) as Markdown in the repo using Astro Content Collections. Blog posts live in Sanity where I can publish without touching code. Best of both worlds: engineers own the structured pages, I own the editorial.</p><h2 id="the-seo-play-that-actually-worked">The SEO play that actually worked</h2><p>We started at Domain Rating 21 with zero organic traffic. Zero indexed keywords. The Nuxt site had been invisible to search for years.</p><p>The strategy came from running 75+ keywords through Ahrefs Keywords Explorer. What we found was extraordinary: 41 keywords with KD 0-3 representing 25,000+ monthly searches. Several high-CPC keywords ($15-30) had literally zero competition. &quot;Generative AI development services&quot; &#x2014; 3,000 monthly searches, KD 1, $20 CPC. Nobody had a dedicated page for it.</p><p>We built 60 pages targeting those keywords. Services, industries, tech stacks, staffing models, blog posts &#x2014; each one targeting a specific keyword cluster with FAQ sections and JSON-LD schema.</p><p>The content architecture follows what we learned from studying competitors. Vention (DR 71, 17.5K traffic) wins by having a dedicated page for every keyword variant. Their MVP page alone captures 5 keyword variants totaling 1,420 monthly traffic from one URL. That&apos;s the model. One page per intent, not five intents crammed onto one page.</p><h2 id="ahrefs-mcp-changed-how-i-work">Ahrefs MCP changed how I work</h2><p>This is the part that surprised me most. Ahrefs has an MCP (Model Context Protocol) integration that connects directly to Claude. I can ask Claude to check our site audit, pull keyword data, analyze competitor backlinks &#x2014; and it queries Ahrefs in real time without me opening the dashboard.</p><p>I&apos;d run a site audit, Claude would parse the results, identify the issues, and generate the exact code fixes &#x2014; meta descriptions that were too long, title tags over 65 characters, broken image references, redirect chains. We went from a health score of 73 to 99 in three sessions. The meta description audit alone covered 31 pages that needed trimming under 155 characters.</p><p>The workflow is: Ahrefs finds the problem through MCP &#x2192; Claude generates the fix &#x2192; Claude Code applies it to the codebase &#x2192; push to GitHub &#x2192; Coolify auto-deploys. The entire cycle from &quot;Ahrefs flagged this&quot; to &quot;fix is live in production&quot; takes minutes, not hours.</p><h2 id="sanity-mcp-for-content-operations">Sanity MCP for content operations</h2><p>Same pattern with Sanity&apos;s MCP integration. When we discovered that pasting blog content from Claude&apos;s chat interface into Sanity Studio was silently converting relative links (<code>/blog/...</code>) to <code>https://claude.ai/blog/...</code> &#x2014; 146 links across 13 posts &#x2014; we wrote a Node script that patched all of them through Sanity&apos;s API in one run.</p><p>But the MCP connection meant I could verify the fix instantly. &quot;Query all posts for any href containing claude.ai.&quot; Zero results. Done. No need to open the Studio, no manual spot-checking.</p><p>We also use Sanity MCP to create blog posts, patch SEO fields, publish batches of documents, and query content for audits. I published 36 blog posts through a combination of MCP creation and Studio editing. All with full body content, proper author references, FAQ sections, and internal cross-links.</p><h2 id="the-legal-pages-nobody-wants-to-write">The legal pages nobody wants to write</h2><p>Every website needs Terms of Service, Privacy Policy, and Cookie Policy. Every founder hates writing them. Most people use a generator that produces generic boilerplate or pay a lawyer $3-5K.</p><p>I found Raj Jha&apos;s <a href="https://github.com/mindheadllc/mill-deterrent-pack?ref=vorobyov.me">mill-deterrent-pack</a> &#x2014; an open-source legal template specifically designed to deter mass-arbitration mills and serial litigants. Three tiers of protection:</p><ul><li><strong>Tier 1:</strong> Pre-dispute notice gate with a 12-item disclosure requirement, class-action waiver, AAA arbitration, specific governing law and venue.</li><li><strong>Tier 2:</strong> 60-day informal resolution period, two mandatory principal-level meetings, fee-arrangement disclosure, 24-month prior-claims history.</li><li><strong>Tier 3:</strong> Pre-merits good-faith review by the arbitrator, claimant-pays cost allocation, bad-faith dismissal with fee-shifting.</li></ul><p>It&apos;s serious legal engineering. The Terms include an ML training prohibition, a $100 liability cap, and indemnification. The Privacy Policy covers GDPR (with CNPD as lead authority since we&apos;re HQ&apos;d in Lisbon), CCPA/CPRA, explicit tracking disclosures, and a &quot;what we don&apos;t use&quot; section. The Cookie Policy documents every cookie by name, purpose, and duration.</p><p>We adapted it for our entity (Icemint LLC d/b/a EltexSoft, Wyoming LLC) and our specific stack (GA4, Cloudflare, Google Fonts). Took an afternoon. The result is more thorough than what most $5K legal reviews produce, and it&apos;s open source.</p><h2 id="ditching-ga4-for-cloudflare-web-analytics">Ditching GA4 for Cloudflare Web Analytics</h2><p>While we were at it, we set up Cloudflare Web Analytics alongside GA4. Server-side, no client JavaScript, no cookies. GDPR-friendly by design &#x2014; there&apos;s nothing to consent to because there&apos;s no tracking pixel in the browser.</p><p>GA4 is still running for now because Search Console integration requires it. But for actual traffic understanding &#x2014; which pages get visited, where people come from, what devices they use &#x2014; Cloudflare&apos;s server-side analytics gives cleaner data without the privacy overhead.</p><p>The Cookie Policy got simpler too. Two Cloudflare operational cookies (<code>__cf_bm</code> for bot management, <code>_cfuvid</code> for rate limiting) plus GA4&apos;s <code>_ga</code> cookies. That&apos;s the complete list. No pixels. No session replay. No heat maps. The privacy policy says what we don&apos;t use, and the list of things we don&apos;t use is longer than the list of things we do.</p><h2 id="infrastructure-860month-for-everything">Infrastructure: $8.60/month for everything</h2><p>The whole stack runs on a Hetzner Cloud VPS (CPX21, $8.60/month). Coolify manages the deployment &#x2014; Nixpacks builds the Astro site, outputs static HTML to an nginx container. Cloudflare sits in front for CDN, WAF, and DDoS protection on the free tier. Sanity&apos;s free tier handles the CMS. GitHub Actions is free. Domain was already paid for.</p><p>Total marginal cost: $8.60/month. Down from whatever Contentful was going to start charging us.</p><p>SSL is Cloudflare Flexible &#x2014; they terminate TLS at the edge and send HTTP to the origin. One less thing to manage on the VPS.</p><h2 id="what-id-do-differently">What I&apos;d do differently</h2><p><strong>Start with the SEO strategy, not the design.</strong> We built pages and then optimized them for keywords. It should be the other way around. The keyword research should dictate the page architecture. Which pages to build, what to call them, how to structure the URLs &#x2014; all of that should come from the data.</p><p><strong>Verify your content proofread by LLMs before it reaches the CMS.</strong> The relative-to-absolute URL resolution bug cost us a full debugging session and a script to fix 146 links. Write content in Markdown, paste Markdown.</p><p><strong>Set up Ahrefs site audit on day one.</strong> We caught 57 errors on the first crawl that were trivially fixable but had been silently hurting us. Broken favicon references, missing meta descriptions, redirect chains &#x2014; all invisible unless you crawl.</p><p><strong>Nginx redirects need absolute HTTPS URLs when Cloudflare Flexible SSL is in front.</strong> Relative redirects resolve to <code>http://</code> at the origin because Cloudflare connects to your server over HTTP. Every <code>rewrite ... permanent</code> needs the full <code>https://yourdomain.com/path/</code> target. We had 25+ redirects silently double-hopping (301 &#x2192; http &#x2192; https) before catching this.</p><p><strong>Disk space on small VPS instances fills up fast with Docker.</strong> Coolify&apos;s Nixpacks builds accumulate overlay layers. We hit 100% disk on a 40GB instance after 16 deployments. Weekly <code>docker system prune -a --volumes -f</code> via cron is mandatory.</p><h2 id="the-numbers-so-far">The numbers so far</h2><ul><li>116 static HTML pages</li><li>36 blog posts with full body content</li><li>13 case studies</li><li>10 industry pages</li><li>9 tech stack pages</li><li>22 course chapters (web edition of my book, &quot;42: The AI Builder&apos;s Stack&quot;)</li><li>Lighthouse mobile: 95+</li><li>Ahrefs health score: 99/100</li><li>Page weight: 5-15 KB HTML + CSS per page</li><li>Build time: 2.66 seconds for 116 pages</li><li>Deploy: push to GitHub &#x2192; Coolify auto-build &#x2192; live in under 3 minutes</li></ul><p>We&apos;re still at DR 21. The content has been live for less than two weeks. Position data starts appearing at 60-90 days. But the foundation is there: 41 keywords with KD 0-3 targeting 25,000+ monthly searches, pure HTML that every bot on the internet can read, and a publishing workflow where I write in Sanity and it&apos;s live in minutes.</p><p>Ask me again in 90 days.</p><hr><p><em>The stack: </em><a href="https://astro.build/?ref=vorobyov.me"><em>Astro</em></a><em> + </em><a href="https://sanity.io/?ref=vorobyov.me"><em>Sanity</em></a><em> + </em><a href="https://hetzner.com/?ref=vorobyov.me"><em>Hetzner</em></a><em> + </em><a href="https://coolify.io/?ref=vorobyov.me"><em>Coolify</em></a><em> + </em><a href="https://cloudflare.com/?ref=vorobyov.me"><em>Cloudflare</em></a><em>. Legal templates: </em><a href="https://github.com/mindheadllc/mill-deterrent-pack?ref=vorobyov.me"><em>mill-deterrent-pack</em></a><em> by Raj Jha. SEO: </em><a href="https://ahrefs.com/?ref=vorobyov.me"><em>Ahrefs</em></a><em>. Code: </em><a href="https://docs.anthropic.com/en/docs/claude-code?ref=vorobyov.me"><em>Claude Code</em></a><em>. The site: </em><a href="https://eltexsoft.com/?ref=vorobyov.me"><em>eltexsoft.com</em></a></p>]]></content:encoded></item></channel></rss>