---
title: "May the 4th be with you. Meet the new freshjuice.dev"
description: "We rewrote freshjuice.dev on Astro + Preact. Faster builds, zero third-party requests, full theme and accessibility support. Happy Star Wars Day."
author: "Alex Zappa"
date: "2026-05-04T12:00:00.000Z"
url: "https://freshjuice.dev/blog/may-the-4th-be-with-you/"
---
# May the 4th be with you. Meet the new freshjuice.dev

May 4, 2026 • Written by [Alex Zappa](https://freshjuice.dev/authors/reatlat/)

May the 4th. The day Star Wars fans pretend they’re not refreshing the merch drops every five minutes. Happy Star Wars Day to everyone quietly mouthing “May the Force be with you” at strangers today.

We didn’t ship merch. We shipped something better: **the entire freshjuice.dev, rebuilt from scratch.**

If the site feels snappier today, that’s why. Under the hood we ripped out Eleventy and Alpine.js, threw away ~6 minutes of build time, and rebuilt the whole thing on Astro 6 + Preact. URL didn’t change. Bookmarks still work. RSS still updates. Tools still work. Everything else under the surface is new.

## Why bother

Honest answer: the old stack was fine. Eleventy 3.x + Tailwind v4 + Alpine was solid and we knew it well. So why rewrite?

Two reasons.

First, **build pipeline pain**. OG image generation alone took ~5 minutes via Puppeteer-driven `node-html-to-image`. We hand-maintained a JSON ledger of pre-compressed images. Pre-commit hooks chained serial JS bundling, then Tailwind, then Eleventy, then Pagefind. It worked. It didn’t have to.

Second, **interactive components were getting hairy**. Our newer SEO tools needed real component-tree state. Alpine was hitting the wall for the complex tools and we didn’t want a hybrid stack. React-flavoured islands hanging off Alpine markup felt like the wrong end of every trade-off. Preact gave us actual components without bringing in React’s weight.

Astro 6 had everything: stable image pipeline, Preact integration, native MDX, islands architecture, build-time everything. The migration was a focused stretch of work and it’s done.

## What changed for you

The boring answer: nothing. Same URLs, same content, same brand. Andrea still writing about marketing, Lorea still writing about AI search, Aleksandr still writing about HubSpot internals.

The interesting answer: **almost every privacy and performance pain point we were quietly carrying is now gone.**

-   **Dark mode, light mode, high contrast: all three persist properly.** Even if you reject every cookie in our consent banner. They live in a single namespaced `localStorage` key (`fj.preferences`) explicitly whitelisted as essential UI state. Your accessibility choices aren’t tracking. They’re you, talking to your browser. Our consent layer used to silently block writes to those keys when you said “reject all.” That bug is fixed.
-   **Zero third-party image requests.** Author photos used to fetch from `gravatar.com` on every page load, leaking your IP to Automattic for every avatar. Now they’re cached locally as WebP at build time. We talk to Gravatar exactly once: when a new author joins. After that, every avatar loads from `freshjuice.dev` only.
-   **OG images are generated, not committed.** Every social card that pops up when you share a FreshJuice link to LinkedIn, X, or Slack is rendered fresh from satori at deploy time. No more 250 hand-maintained webp files cluttering git history. Open the [`/tools/metadata-checker/`](https://freshjuice.dev/tools/metadata-checker/) on any of our pages. Every share preview is one of these.
-   **Responsive images by default.** Every blog photo now ships a srcset of breakpoint variants, so a 360px phone fetches a 360px image, not the original 2000px hero.
-   **The print stylesheet actually prints.** Hit ⌘P on any blog post. Comes out as ink on paper, clean typography, no wasted toner on dark-mode backgrounds. We even slipped a tiny FreshJuice attribution mark into the printed header.
-   **Accessibility held the line.** WCAG 2.1 AA contrast ratios untouched. Mobile drawer respects dark mode (a regression we introduced and fixed within hours). All keyboard nav still works.
-   **Every tool got reworked.** All 22 [Fresh Tools](https://freshjuice.dev/tools/) (SEO Analyzer, link checker, metadata inspector, llms.txt generator, contrast checker, Am I Responsive, the lot) got rebuilt as proper Preact components instead of the half-Alpine hybrids they used to be. Faster, snappier, less weirdness on edge inputs. [Am I Responsive](https://freshjuice.dev/tools/am-i-responsive/) now has a one-click “picture mode” + Save-to-PDF that actually works. The HTML↔Markdown pair render previews in shadow DOM so author CSS can’t bleed into the tool chrome. Worth a click around if you haven’t visited the tools page in a while.
-   **Component loading got smarter.** The old setup shipped one ~200 KB `main.js` bundle to every page: Alpine.data for all 22 tools, mobile drawer, theme toggles, search palette, all of it, even on a static blog post that needed approximately none of it. Astro’s islands architecture inverted that. Each interactive component ships as its own tiny JS chunk, loaded only when actually needed. A blog post now blocks first paint on ~2 KB of gzipped JS (page bundle + the BaseLayout entry script). The consent banner (~13 KB gz) loads during browser idle time, after the page is already painted and interactive. The search palette (~24 KB gz) doesn’t fetch until you press Cmd+K or click the search trigger. The 21 tools you’re not using on this blog post? Their JS never loads at all.

## Side of the Force

Bringing it back to today, the operating principle for the rewrite was simple: **everything bakes on our side until you opt in.**

No third-party fonts. No tracking pixels by default. No marketing cookies. No share-button widgets phoning home to LinkedIn. YouTube embeds are click-to-load on the nocookie domain, so they don’t fire until you press play.

We’re not perfect about it. We do run HubSpot analytics, but only **after** you accept cookies in the consent banner. Hit “Reject all” and HubSpot never loads. Hit nothing and HubSpot never loads either (default state is reject; we don’t pre-tick analytics).

You can audit the default state yourself. Open DevTools → Network, hit any FreshJuice page, scroll, click around. Without a consent decision, the only domain that talks back is `freshjuice.dev`. That’s the floor we hold.

Other studios call this an architecture. We call it the bare minimum.

## Side quest: we open-sourced our search palette

While rebuilding, we tripped over a real gap in the Astro ecosystem: **there’s no good Cmd+K search palette for Astro sites.** Most “search for Astro” search results land you on either client-only Lunr hacks, paid SaaS embeds (Algolia, Mintlify), or “here’s a 200-line component, copy-paste it into your project” blog posts.

So we built [`@freshjuice/astro-search-plugin`](https://github.com/freshjuice-dev/astro-search-plugin?utm_source=freshjuice.dev). Apache 2.0. Type-safe. Build-time index via [Orama](https://orama.com/?utm_source=freshjuice.dev), client-side query, drop-in `<astro-search-palette>` web component. Works with vanilla JS, Vue, Svelte, Solid, React. Zero runtime CDN, zero telemetry. It’s what powers the Cmd+K on this site you’re reading right now.

If you’ve been hand-rolling search for an Astro site, save yourself the afternoon.

## The numbers, for the curious

End-to-end deploy time on Cloudflare Pages, from `git push` to “live on freshjuice.dev”:

| Run | Eleventy | Astro |
| --- | --- | --- |
| Cold (no cache) | **5 min 11 s** | **~1 min 30 s** |
| Hot (cached deps + images) | 1 min 56 s | ~1 min 30 s |
| Output `dist/` size | 117 MB | 43 MB |

The **really** interesting number isn’t the cold-start speedup (3.3×). It’s that Astro deploys at the **same speed regardless of cache state**. Eleventy was a coin flip: 2 minutes if you got lucky with the cache layer, 5 if you didn’t. Predictability matters more than peak when you’re shipping multiple PRs an hour.

Output also dropped 2.7×: partly Astro’s smarter image variant generation (we no longer ship every blog photo at five resolutions), partly killing 250 hand-committed OG webp files in favour of build-time generation.

## Where to find us (not on X)

Quick housekeeping: **we left X.** The timeline turned into a swarm of bots and bored humans screaming “Grok, explain what 2 + 2 means” at each other. AI slop and zombieland in one bottle. We logged off, we’re not coming back, the account is dead.

Where we actually are:

-   [@freshjuice-dev on GitHub](https://github.com/freshjuice-dev/?utm_source=freshjuice.dev): code, issues, fork-and-yell-at-us
-   [FreshJuice on LinkedIn](https://www.linkedin.com/company/freshjuice?utm_source=freshjuice.dev): release notes + blog announcements for the corporate-aware
-   [@FreshJuiceDev on Mastodon](https://mastodon.social/@FreshJuiceDev?utm_source=freshjuice.dev): the casual stuff that used to live on X, minus the algorithm and the bots

And the canonical thing: [RSS](https://freshjuice.dev/feed.xml) / [JSON Feed](https://freshjuice.dev/feed.json). Subscribe once, get notified forever, no platform in between.

## What’s next

Same things we were already doing. More [tools](https://freshjuice.dev/tools/). More [posts](https://freshjuice.dev/blog/). Whatever Andrea and Lorea decide to write next. The migration was infrastructure work. The actual product is still the same FreshJuice you know.

If you find anything broken, ping us via [contact](https://freshjuice.dev/contact/) or open an issue on the [GitHub repo](https://github.com/freshjuice-dev/freshjuice-website?utm_source=freshjuice.dev). Everything’s open source. Read the build script. Fork it. Yell at us.

May the 4th be with you,  
[Alex](https://alex.zappa.dev)

-   [#FreshJuice](https://freshjuice.dev/tags/freshjuice/) ,
-   [#Privacy](https://freshjuice.dev/tags/privacy/) ,
-   [#TechnicalSEO](https://freshjuice.dev/tags/technical-seo/) ,
-   [#WebsiteOptimization](https://freshjuice.dev/tags/website-optimization/)
