# Alex Zappa URL: https://alex.zappa.dev ## Hi, I'm Alex. URL: https://alex.zappa.dev I am writing about my experiences as a naval navel-gazer and builder of things. # Hi, I'm [Alex](/about/). Dad. Husband. Proud servant of two cats. By day, I craft web experiences. By night, I craft more web experiences. Weekends? Bold of you to assume I stop. I run [FreshJuice](https://freshjuice.dev/), a dev studio for teams who want fast, modern web. All juice, no pulp. 🍊 I've spent 15+ years building for companies like [Iru](https://www.iru.com), [Orange Marketing](https://orangemarketing.com/), [Kandji](https://www.kandji.io/), [Trovata](https://www.trovata.io/), and [DataFox](https://www.oracle.com/corporate/acquisitions/datafox/). When I'm not coding, I'm probably traveling, playing video games, or being bossed around by cats. ![Random image](https://picsum.photos/seed/default/240/240/)[What is this?](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) ## Recent Posts August 22, 2026 ### [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. August 14, 2026 ### [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. August 12, 2026 ### [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. June 23, 2026 ### [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. ## Featured Posts December 28, 2025 ### [2025 year in review: a year of building, learning, and gratitude](/blog/year-in-review-2025/) Reflecting on a year filled with code, open-source contributions, and the amazing people who made it all worthwhile. December 1, 2024 ### [webChronicle: your personal Wayback Machine](/blog/webchronicle/) A web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. November 29, 2021 ### [My favorite CSS hacks](/blog/my-favorite-css-hacks/) Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. --- ## I'm Alex, a code machine who loves building stuff URL: https://alex.zappa.dev/about About Alex Zappa. ![Alex Zappa at Work](/_astro/alex-zappa-at-work.DgalHZji_1MwoLf.webp) # I'm Alex, a code machine who loves building stuff Dad. Husband. Proud servant of two cats. I run [FreshJuice](https://freshjuice.dev), a dev studio for teams who want fast, modern web. All juice, no pulp. 🍊 ## What I do By day, I craft web experiences. By night, I craft more web experiences. Weekends? Bold of you to assume I stop. I've spent 15+ years building for companies like [Iru](https://www.iru.com), [Orange Marketing](https://www.orangemarketing.com), Kandji, Trovata, and DataFox. HubSpot CMS is my main playground: themes, modules, integrations, the whole ecosystem. My toolkit: JavaScript, Node.js, Eleventy, Alpine.js, TailwindCSS, and whatever else gets the job done. I care about performance, clean code, and making things that actually work for marketing teams. ## FreshJuice What started as a side project turned into something people actually use. [FreshJuice](https://freshjuice.dev) is now a studio and product platform: open-source themes, premium modules, and custom development for teams who want modern HubSpot without the BS. ## What people say > "I worked with Alex for almost a decade, and he is the best web developer I've ever worked with. I've never seen anyone work so fast. Anytime I asked him to do something ambitious or borderline crazy, his default reaction was always that anything is possible and he'd find a way. And he always did." > "Alex has been a really great asset in the many projects he has worked with us on over the past 5 years. He is always enthusiastic, focused and delivers to the highest quality. He is responsive, helpful and always eager to pass his knowledge to others, making him a great collaborator and mentor." > "Alex was one of the most talented and reliable website developers I've had the pleasure of working with. His problem-solving skills, calm under pressure, and collaborative spirit made even the toughest projects feel achievable." [More recommendations on LinkedIn →](https://www.linkedin.com/in/reatlat/details/recommendations) ## Beyond the keyboard When I'm not coding, I'm probably cooking something, working on a woodworking project, or planning the next travel adventure. I have a brilliant 19-year-old son who [followed my path into software engineering](https://zapparov.dev/) (proud dad moment). Two cats keep me company during late-night coding sessions (and by "keep me company" I mean "demand food at 3am"). I'm into open source, reverse engineering things to understand how they work, and sharing what I learn through this blog. I speak English, Spanish, Catalan, and Russian. ## Let's connect Got a project? Need juice? → [freshjuice.dev](https://freshjuice.dev) Or just say hi: - [GitHub](https://github.com/reatlat) - [LinkedIn](https://linkedin.com/in/reatlat) - [Email](mailto:alex@zappa.dev) --- ## Blog Archive URL: https://alex.zappa.dev/blog Writing about systems, privacy, and software. # Blog Archive ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. - 06/23 [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. - 06/22 [Why I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. - 03/10 [HubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) HubSpot's cookie consent banner secretly loads Google Fonts before you even consent, leaking your IP to Google. Here's how I fixed it with a JavaScript proxy hack. - 01/11 [Migrating from Cloudflare Pages to Workers (and cleaning up the mess)](/blog/cloudflare-pages-to-workers-migration/) My experience merging an 11ty static site and API into a single Cloudflare Worker, plus a bash script to delete old Pages deployments when the dashboard won't let you. - 01/03 [How I replaced my ISP router and extracted PPPoE credentials using a bridge sniffer](/blog/isp-router-replacement/) A guide to replacing your Digi España router with your own hardware by capturing PPPoE credentials through packet sniffing. Includes bridge setup, Wireshark analysis, and GL.iNet configuration. ## 2025 - 12/28 [2025 year in review: a year of building, learning, and gratitude](/blog/year-in-review-2025/) Reflecting on a year filled with code, open-source contributions, and the amazing people who made it all worthwhile. - 10/08 [Guest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) The future of search isn't keywords, it's context. In my guest post for Orange Marketing, I explored how AI is reshaping SEO and how marketers and web developers can adapt. ## 2024 - 12/06 [How to manage multiple SSH keys for multiple GitHub accounts on one computer](/blog/how-to-manage-multiple-ssh-keys-for-multiple-github-accounts-on-one-computer/) Learn how to set up and manage multiple SSH keys on a single computer to seamlessly work with multiple GitHub accounts. Step-by-step guide for developers. - 12/01 [webChronicle: your personal Wayback Machine](/blog/webchronicle/) A web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. - 06/23 [Upgrading to Eleventy v3](/blog/upgrading-to-eleventy-v3/) I took some time this week to upgrade my site to the newest version of Eleventy. Here's what I learned. - 04/09 [Custom user avatars for WordPress](/blog/custom-user-avatars-for-wordpress/) Adding custom avatar functionality to your WordPress site. Simple and quick solutions to make your website even more attractive to users! - 01/07 [How to use structured data (schema markup) in HubSpot to improve SEO](/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo/) How to use Structured Data, also referred to as Schema Markup, inside of HubSpot that is populated using HubL variables for use to improve SEO. ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. - 08/13 [Migrating from WordPress to 11ty: enhancing website performance and control](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) Discover the benefits of using 11ty for static website generation, including improved performance, simplicity, and control over your website. Take your online presence to new heights with 11ty. ## 2022 - 01/21 [How to change App Store preferred Language 2022](/blog/how-to-change-app-store-preferred-language-2022/) Resolve your AppStore language issue on macOS! Despite numerous solutions, I discovered a simple fix for the problem. Change your VPN to UK location, log out of AppStore, force close it, and open again. After logging in, AppStore will switch to English. ## 2021 - 11/29 [My favorite CSS hacks](/blog/my-favorite-css-hacks/) Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. - 11/21 [Detect dark or light system mode](/blog/detect-dark-or-light-system-mode/) Learn how to use prefers-color-scheme CSS media feature to detect user color theme preferences. See examples using media query and JavaScript. Also, track color mode with Google Tag Manager and Google Analytics. - 10/27 [Mobile friendly Responsive images with CSS](/blog/mobile-friendly-responsive-images-with-css/) Learn how to make responsive images in HTML and CSS. Discover how to prevent images from going outside the container. Find solutions for WordPress and HubSpot. - 10/22 [Keep Track UTM parameters for HubSpot forms like Cookie Monster](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/) Track UTMs and store them using cookies instead of sessionStorage. Use JavaScript Cookie library and add the code to the website footer. - 10/16 [Track UTM parameters for HubSpot forms like a BOSS](/blog/track-utm-parameters-for-hubspot-forms-like-a-boss/) Learn how to create and track marketing campaigns by using tracking URLs with HubSpot. Pass UTM parameters to hidden form fields using JavaScript. Store and feed form fields in sessionStorage. Install the code in the site footer of your HubSpot website. ## 2019 - 04/21 [WP plugin Elementor Multidomain Support](/blog/wp-plugin-elementor-multidomain-support/) A plugin for Elementor Page Builder that adds multi-domain support for Multilanguage websites. Available on WordPress repository and GitHub. - 04/14 [How to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) Block visitors by country using NGINX and the GeoIP module. Install the module and database, configure the nginx.conf file, and test the configuration before restarting nginx. Instructions with examples provided. - 04/10 [How remove .DS\_Store files from a Git repository?](/blog/how-remove-dsstore-files-from-a-git-repository/) Learn how to remove .DS\_Store files from your Mac OS X repository. Use commands to find and remove the files. Update .gitignore for future prevention. Check gitignore.io for more configurations. - 01/19 [Campaign URL Builder Introducing the Shortcodes](/blog/campaign-url-builder-introducing-the-shortcodes/) Introducing the first release of shortcodes for Campaign URL Builder plugin. Shortcodes can be customized with various attributes and can generate forms on the front-end. Bitly API key is required. Shortcodes can be styled and have optional parameters. Examples provided for simple, advanced, and PHP usage. - 01/19 [How do I find my Bitly OAuth access token?](/blog/how-do-i-find-my-bitly-oauth-access-token/) Learn how to sign up for a Bitly account and generate an access token for the Bitly API. Follow these steps to get started. - 01/14 [Transitioning Google URL shortener to Bitly](/blog/transitioning-google-url-shortener-to-bitly/) Campaign URL Builder plugin is moving to Bitly API endpoint; support for goo.gl URL shortener will be turned down in March 2019. Previously created links will redirect. - 01/01 [WP plugin Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) Learn how to track Custom Campaigns in Google Analytics with this plugin. Easily create short links and share them on social networks. Get and install the plugin for WordPress from the repository or GitHub. Use the shortcode to generate shortlinks on any page. May the 4th be with you! ## 2018 - 11/09 [GitKraken – cross-platform editor for git repos](/blog/gitkraken-cross-platform-editor-for-git-repos/) GitKraken is a cross-platform git interface written in electron and open libraries, offering merge conflict tools and a task-tracking system. Get a $20Off discount for a one-year subscription. ## 2016 - 11/08 [Adding subscribers to a list using Mailchimp’s API v3](/blog/adding-subscribers-to-a-list-using-mailchimps-api-v3/) Learn how to add and update list members in MailChimp's API v3 using a PUT request. Includes code example and tips. May the 4th be with you! --- ## Adding subscribers to a list using Mailchimp’s API v3 URL: https://alex.zappa.dev/blog/adding-subscribers-to-a-list-using-mailchimps-api-v3 Learn how to add and update list members in MailChimp's API v3 using a PUT request. Includes code example and tips. May the 4th be with you! Based on the [List Members Instance docs](https://mailchimp.com/developer/marketing/api/list-members/), the easiest way is to use a PUT request which according to the docs either “adds a new list member or updates the member if the email already exists on the list”. Furthermore, API key is definitely not part of the [JSON schema](https://us9.api.mailchimp.com/schema/3.0/Lists/Members/Instance.json) and there’s no point in including it in your JSON request. Also, you can use CURLOPT\_USERPWD for basic HTTP auth as illustrated in below. I’m using the following function to add and update list members. You may need to include a slightly different set of merge\_fields depending on your list parameters. ``` 'angelina.jolie@example.com', 'status' => 'subscribed', 'firstname' => 'Angelina', 'lastname' => 'Jolie' ]; syncMailchimp($data); function syncMailchimp($data) { $apiKey = 'your api key'; $listId = 'your list id'; $memberId = md5(strtolower($data['email'])); $dataCenter = substr($apiKey,strpos($apiKey,'-')+1); $url = 'https://' . $dataCenter . '.api.mailchimp.com/3.0/lists/' . $listId . '/members/' . $memberId; $json = json_encode([ 'email_address' => $data['email'], 'status' => $data['status'], // "subscribed","unsubscribed","cleaned","pending" 'merge_fields' => [ 'FNAME' => $data['firstname'], 'LNAME' => $data['lastname'] ] ]); $ch = curl_init($url); curl_setopt($ch, CURLOPT_USERPWD, 'user:' . $apiKey); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode; } ``` May the 4th be with you, Alex - [NextGitKraken – cross-platform editor for git repos](/blog/gitkraken-cross-platform-editor-for-git-repos/) --- ## Guest post on Orange Marketing: AIEO is the new SEO URL: https://alex.zappa.dev/blog/aieo-orange-guest The future of search isn't keywords, it's context. In my guest post for Orange Marketing, I explored how AI is reshaping SEO and how marketers and web developers can adapt. The future of search isn’t keywords, it’s context. Seems like no one really _Googles_ anymore. We just ask AI. Search has evolved, and so should the way we create and optimize content. In this guest post for [Orange Marketing](https://www.orangemarketing.com), I explored how marketers and modern web developers can rethink their strategies for the age of _AI-powered search_, where trust, structure, and machine-readable data become the new SEO fundamentals. 👉 You can read the full article on Orange Marketing’s blog: [AIEO / AEO is the New SEO: What HubSpot Marketers Should Know](https://blog.orangemarketing.com/aieo-aeo-is-the-new-seo-what-hubspot-marketers-should-know) Thanks for reading, and stay fresh out there. 🍊 May the 4th be with you, Alex - [PreviousHow to manage multiple SSH keys for multiple GitHub accounts on one computer](/blog/how-to-manage-multiple-ssh-keys-for-multiple-github-accounts-on-one-computer/) - [Next2025 year in review: a year of building, learning, and gratitude](/blog/year-in-review-2025/) --- ## Why I migrated my stack from Eleventy to Astro 6 URL: https://alex.zappa.dev/blog/astro6-migration Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. Two years ago I [upgraded my site to Eleventy v3](/blog/upgrading-to-eleventy-v3/). The future looked bright. Eleventy (11ty) was lean and fast, and it never tried to have an opinion about how I built things. Then about a year later it landed firmly inside Font Awesome, the icon company where Zach Leatherman (Eleventy’s creator) works. At the time that sounded like good news. More resources, a bigger team, a project that could actually pay its bills. I figured any monetization would show up as an optional add-on, and honestly, fair enough. You build something valuable, you should get paid for it. I don’t want to sound alarmist. But earlier this year the Eleventy possum quietly changed. The classic red balloon got swapped for softer, paler pastels. Then came the announcement: Eleventy as I knew it was being folded into BuildAwesome, part of the bigger Font Awesome platform. Technically nothing was wrong with that. A little seed of doubt got planted anyway. ![The classic Eleventy possum with its red balloon morphing into a corporate BuildAwesome version with a pastel-green balloon](/_astro/11ty-metamarphose.3gBCc9Fw_ZJ42UI.webp) ## What actually stings is the open-source part First, some skin in the game, because it matters for what comes next. I’m not throwing rocks from outside the fence. I back Eleventy on [Open Collective](https://opencollective.com/reatlat), and I’m a listed author in the [11ty community](https://www.11ty.dev/authors/). I’ve shipped sites with it, written about it here, and put money behind it. So when I say the next part, read it as someone who’s invested, not someone looking for a reason to leave. I’ll be honest, I never loved Font Awesome. As a web dev I’ve spent way too many hours explaining to clients that no, you don’t need the whole thousand-icon collection. You’re using five of them. _Five._ That conversation gets old fast. But the icons aren’t really the point. What gets to me is bigger and quieter than that. It’s the drift you can feel across a lot of open source lately. Something starts as a labor of love, the community shows up, and then it usually goes one of two ways. Either it slowly gets abandoned, or it gets pulled toward more and more monetization until the original spirit has worn thin. Almost never in one big dramatic move. It just erodes, a bit at a time. Eleventy doesn’t feel abandoned. But the center of gravity moved somewhere else. The license is still open, BuildAwesome is distributed openly, and I don’t even mind the soft paywalls showing up around the edges. It’s the energy I miss. A year ago the community was everywhere, building things and jumping into each other’s PRs. Now PRs sit for months, and that buzz feels like it’s happening in some other room. Go look at the [community showcase](https://github.com/11ty/11ty-community/issues) right now. There are around thirty open “Built with Eleventy” submissions, people proudly showing off sites they made, and the oldest ones have been sitting there since February with not a single reply. Thirty-odd little “hey, I built this” posts, and nobody’s home to say nice work. That’s the part that gets me. Not a paywall. A room that used to be full and now echoes. And here’s the thing that really made it click: that backlog is exactly what I tried to fix. I opened a [tiny PR](https://github.com/11ty/11ty-community/pull/1023#issuecomment-4655234373) to automate the whole flow, check that a submitted site is genuinely built with Eleventy, then add it to the collection without anyone having to babysit every issue by hand. Zero dependencies, just a few lines of plain Node. I was, honestly, trying to make the maintainer’s life easier and clear that queue. The reply went looking for complexity instead: use an existing utility, `@11ty/find-generator`, which drags in `cheerio` and `sharp` as transitive deps. Reasonable on its own, sure, but the exact opposite of the minimalism Eleventy built its whole identity on. Reach for the library, eat the dependencies. I kept mine dependency-free and left the final call to the maintainer. It’s still sitting there, open, along with everyone else’s. And to be clear, this isn’t a callout. Zach built something genuinely great, and he has every right to make a living from it. It’s closer to grief than anger, if that makes sense. The open-source kind of grief. You watch something you love change shape, and there’s nobody to be mad at, just a direction it’s drifting in. That’s the part that stings. ## On benchmarks and fairness The number everyone keeps quoting comes from [Zach’s build benchmark](https://www.zachleat.com/web/build-benchmark/), and credit where it’s due, it’s a genuinely useful test. But two things about it never quite sat right with me. First, what it actually measures: markdown files and nothing else. No images, no CSS, no pagination. Just how fast each engine turns plain text into HTML. At 4,000 files Hugo finishes in well under a second and Eleventy is the quickest of the JavaScript bunch. Impressive numbers. They’re also nothing like a real site, where the slow part is almost always images and assets, not parsing text. Putting that next to Astro, which comes with image optimization and asset bundling already wired in, is a bit like timing two runners when only one of them is carrying the groceries. Second, and this is the one that really gets me: that benchmark is from 2022. It pits Eleventy 1.0 against **Astro 1.0.1**, a version that had been stable for about three weeks when the test ran. Judging a four-year-old, preview-era Astro and calling the race is like timing a sprinter on their very first day in the sport. Astro 6 is a completely different machine now: Vite under the hood, islands, parallelized builds, image optimization baked in. Here’s what actually got me curious, though, and it wasn’t a livestream. It was migrating [FreshJuice](https://freshjuice.dev) itself. After moving it to Astro I braced for slower deploys, because Astro does more work per build, so surely my Cloudflare build times would climb. They didn’t. They stayed inside the margin of error of the old Eleventy setup. Same ballpark, more features, zero penalty. That mismatch between what the famous benchmark implied and what my own deploy logs showed is the whole reason I went and measured it properly. So I built my own, [open and reproducible](https://github.com/reatlat/ssg-benchmarks). Same content for every engine, generated from a fixed seed, with the real ingredients this time: 500 posts, images processed into responsive variants, Tailwind, pagination, tags. Then I timed cold, warm, and incremental builds, five runs each, on an M3 Max with the whole CPU off the leash. And the result genuinely surprised me. On a cold build, the one CI runs every single time you deploy, Astro was the fastest of all three: 27 seconds, ahead of Eleventy (34s) and even ahead of Hugo (31s). Hugo’s legendary speed is real, but it’s _markdown-parsing_ speed. The moment you make every engine resize the same images into responsive variants, that lead evaporates, because now the bottleneck is image processing, and Astro’s sharp pipeline happily spreads across all 16 cores. Eleventy takes its wins where it always has: warm and incremental rebuilds (around 2.3s versus Astro’s 4s) and memory, where it peaked at 319 MB while Astro climbed to nearly a gigabyte. So this isn’t a knockout in either direction. It’s trade-offs. Astro does far more work per build and still wins the build that matters most for shipping; Eleventy stays lean and snappy on the local save-and-refresh loop. One honest caveat, and it’s the whole point of this post: those numbers are from a 16-core M3 Max with nothing throttled. Cap the cores the way a real CI builder does, and the picture shifts again. So I re-ran the exact same benchmark inside the resource limits of a Cloudflare build container (Free is 2 vCPU, Paid is 4 vCPU, both 8 GB). Here’s the cold build, the one that runs on every deploy, as the mean of five runs: | Environment | Eleventy | Astro | Hugo | | --- | --- | --- | --- | | M3 Max — 16 cores, unthrottled | 33.9s | 27.1s | 30.9s | | Cloudflare Paid — 4 vCPU / 8 GB | 45.4s | 35.6s | 53.0s | | Cloudflare Free — 2 vCPU / 8 GB | 58.4s | 66.3s | 106.1s | And there it is. Hand Astro all 16 cores and it wins outright, because its image pipeline parallelizes beautifully. Squeeze it down to the 2 vCPUs a free CI runner actually gives you, and the advantage flips: Eleventy retakes the cold build (58s vs 66s) because it isn’t relying on parallelism it no longer has. Hugo, the supposed speed king, falls off a cliff once it has to resize real images on two cores. This is exactly why a benchmark on 4,000 empty files tells you almost nothing about your actual deploy. The hardware budget changes the winner. Warm rebuilds tell a calmer story. This is the local save-and-refresh loop, and here the lighter runtimes stay ahead no matter how many cores you give them: | Environment | Eleventy | Astro | Hugo | | --- | --- | --- | --- | | M3 Max — 16 cores, unthrottled | 2.4s | 4.4s | 1.9s | | Cloudflare Paid — 4 vCPU / 8 GB | 2.5s | 4.3s | 1.6s | | Cloudflare Free — 2 vCPU / 8 GB | 2.6s | 4.9s | 2.1s | Notice how little these move. Warm rebuilds barely care about core count, because there’s no big image batch to parallelize, so Astro’s Vite layer just carries a bit more constant overhead (5s versus 2s). Incremental rebuilds tracked these almost exactly. Memory was the one axis that never flipped in any environment: Eleventy held around 320–380 MB throughout, while Astro lived close to a gigabyte. Pick your trade-off. ## DIY vs. done Using Eleventy these days feels like running Arch Linux. Here are your ingredients, now go build your own engine. And I mean that with love. I ran Linux as my daily driver until 2017 (btw, I use Arch). But you know the thing about Linux on the desktop. You burn real hours fixing whatever broke after an update, or trying to work out why something that obviously should work just
 doesn’t. Astro 6 is the macOS version of that. It just works. You get full control and everything out of the box at the same time. When there’s client work to ship, you don’t have time to debug your own operating system. You need it to get out of the way. ![PageSpeed Insights score for this site running on Astro 6 — all 100s](/_astro/astro-speed.rHwfT3fW_2g3KrX.webp) The Islands architecture on its own basically sold me. I don’t have to sit there thinking about what loads on the frontend versus the backend, because Astro handles it natively. No workarounds, no ceremony. Want a CMS down the line? Bolt on something like [CloudCannon](https://cloudcannon.com/) and you’ve got an actual deliverable to hand a client. I still like working with files directly, but the real thing is this: I just don’t want to spend my life on build tooling anymore. With Astro it’s faster and simpler, and that’s enough. That’s the real reason [FreshJuice](https://freshjuice.dev) moved over. I’d run into a couple of genuine growth and scaling questions, and staying on Eleventy meant piling up technical debt just to answer them. (I’d already [come to 11ty from WordPress](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) years back, so this wasn’t a decision I took lightly.) Astro was just the cleaner bet. To be clear, I’m not ripping Eleventy out of everything I own. For a lot of my projects it’s still the perfect tool, and I have zero plans to touch them. This isn’t a breakup, it’s one project outgrowing one stack. What I am doing is watching its future with a pretty skeptical eye. And going forward, new projects are Astro by default. I’m done spending evenings hand-wiring a build pipeline and babysitting plugin configs just to land where Astro already starts. The fiddling was fun once. Now I’d rather ship. ## A bet on the future Earlier this year Cloudflare bought Astro, and this is where I get genuinely optimistic. I [already run my stack on Cloudflare](/blog/cloudflare-pages-to-workers-migration/), and they’ve spent years pushing the web to be faster and more open. With them behind it, I think Astro can pull off something I didn’t expect to find myself rooting for. It could actually challenge, or at least seriously rattle, the reigning king of websites: WordPress. Pair Astro with whatever CMS the team is cooking up, and that future feels a lot closer than it has any right to. ![A purple Astro rocket lifting off into the sky as a cartoon Eleventy possum waves goodbye from the ground](/_astro/astro-liftoff.B14B3UTZ_jf2qX.webp) I’m rooting for them. Moving [FreshJuice](https://freshjuice.dev) to Astro was the right call. No regrets. **Benchmark repo:** [github.com/reatlat/ssg-benchmarks](https://github.com/reatlat/ssg-benchmarks) May the 4th be with you, Alex - [PreviousHubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) - [NextAstro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) --- ## Astro 7 dropped the day after I finished my Astro 6 migration URL: https://alex.zappa.dev/blog/astro7-update The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. Yesterday I published a [3,000-word post](/blog/astro6-migration/) about migrating my entire stack from Eleventy to Astro 6. Benchmarks, philosophy, the whole thing. I hit publish, felt good about it, and went to sleep. I woke up to Astro 7. Not a release candidate. Not a beta. The actual `astro@7.0.0`, sitting on npm, looking at me like it knew exactly what it was doing. ![Speedometer comparison showing Astro 6 at 70% and Astro 7 at 95% build performance](/_astro/astro7-migration.Ba1IrLi8_gxuOx.webp) ## The upgrade command is a trap Astro ships an `npx astro upgrade` command that handles the migration for you. It updates dependencies, rewrites config, and prints a cheerful summary of what changed. I ran it. It worked. The output looked clean. I thought I was done. I was not done. The upgrade command is good at the mechanical stuff: bumping version numbers, swapping deprecated APIs for their replacements, updating the markdown config to use the new `unified()` processor. What it can’t do is tell you that your dependency tree is about to implode, or that your CSS is going to silently lose half its rules. That part is on you. ## Sharp: the native binary that couldn’t The first thing that broke was `sharp`. Not because of Astro 7 directly, but because I’d bumped it to `0.35.2` (the latest) while `astro-compress` and `miniflare` (inside `wrangler`) both pinned `0.34.5`. npm dutifully installed three copies of sharp at different versions, and the `0.34.5` one tried to build from source because it has no prebuilt binaries for macOS 26 (Darwin 25.5.0). The build-from-source path needs `node-addon-api` and `node-gyp`, which I didn’t have, so it failed. The error message was the kind of thing that makes you question your career choices: ``` sharp: Attempting to build from source via node-gyp sharp: Please add node-addon-api to your dependencies ``` The fix was npm `overrides`, forcing every transitive sharp dependency to resolve to the project’s direct `0.35.2`: ``` "overrides": { "astro-compress": { "sharp": "$sharp" }, "miniflare": { "sharp": "$sharp" } } ``` The `$sharp` syntax tells npm “use whatever version the project itself declares.” One copy of sharp, one set of prebuilt binaries, zero build-from-source nightmares. This is the kind of thing that works perfectly on your machine and then explodes in CI because the Linux runner has a different glibc. Overrides are your insurance policy. ## The media queries that vanished This one was weirder. The site built fine locally. Tailwind was working. Dark mode toggled. Everything looked correct in dev. But the production build, the one that runs through `astro-compress`, was missing responsive behavior. Tables didn’t stack on mobile. The CodePen embed wrapper didn’t adjust at small screens. I diffed the CSS output and found the smoking gun. My hand-written media queries: ``` @media (max-width: 640px) { 
 } @media (min-width: 640px) { 
 } ``` Had become, in the production CSS file, absolutely nothing. Gone. Deleted. Not minified. Erased. The chain of events is a beautiful little tragedy in three acts: 1. **Astro 7 switched its compiler from Go to Rust.** The new Rust compiler uses Lightning CSS under the hood for CSS processing. 2. **Lightning CSS transforms standard media queries into range syntax.** `max-width: 640px` becomes `width<=640px`. This is valid CSS Media Queries Level 4. It’s the modern way to write them. Lightning CSS is doing the right thing. 3. **csso, the CSS minifier inside astro-compress, doesn’t understand range syntax.** It sees `@media (width<=640px)` and thinks “I don’t know what this is, probably redundant, delete it.” So the pipeline was: write valid CSS → Lightning CSS modernizes it → csso eats it. The Go compiler never did that transformation, so csso never choked on it. The Rust compiler does, and suddenly your responsive layout is gone. The fix was one line in `astro.config.mjs`: ``` (await import('astro-compress')).default({ CSS: false }), ``` astro-compress still minifies HTML, JavaScript, SVG, and images. It just leaves CSS alone. The Tailwind output is already well-optimized, and the ~40 KB CSS file isn’t worth fighting a minifier over. ## Tailwind v4 adjustments Astro 7 ships with first-class Tailwind v4 support via the `@tailwindcss/vite` plugin. The migration was mostly smooth. The `@theme` block, `@layer` directives, and `@custom-variant` for dark mode all carried over cleanly. The main thing I had to adjust was the `@import` and `@source` directives at the top of `global.css`: ``` @import "tailwindcss" source("../"); @source "../**/*.astro"; ``` Tailwind v4 uses CSS-based configuration instead of a separate `tailwind.config.js`, which is honestly a win. One less file to maintain. ## Was this Astro’s fault? No. Let me be clear about where the bugs actually live: - **The sharp conflict** is a dependency management problem. astro-compress and miniflare pin older sharp versions that don’t have binaries for new macOS releases. npm overrides are the standard fix. - **The media query bug** is in csso, not Astro. csso hasn’t had a release in over a year and doesn’t support CSS range syntax. Lightning CSS (which astro-compress already bundles as a dependency) handles it fine; the fix would be for astro-compress to switch its CSS minifier from csso to lightningcss. - **The Go→Rust compiler change** is a feature, not a bug. It’s faster, it enables new optimizations, and the range-syntax transformation is correct modern CSS. The only problem is that a downstream tool hasn’t caught up. ## The bright side Here’s the thing: after fixing those two issues, everything else just worked. The build is fast. The output is clean. The site scores 100s on PageSpeed Insights. Astro 7’s new compiler is genuinely quicker, and the Rust-based pipeline feels snappier even on warm rebuilds. I spent maybe two hours debugging total. For a major version bump that landed the day after I finished a full stack migration, that’s honestly not bad. The upgrade command handled 90% of the mechanical work. The remaining 10% was dependency tree surgery and a one-line config change. I’m not even mad. The timing is objectively hilarious. But the platform is solid, the fixes were straightforward, and I’d rather debug two issues in an evening than spend a weekend hand-wiring a build pipeline. Astro 7 is a good release. Just
 maybe wait a day after your migration post before you run the upgrade. May the 4th be with you, Alex - [PreviousWhy I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) - [NextClaude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) --- ## Campaign URL Builder Introducing the Shortcodes URL: https://alex.zappa.dev/blog/campaign-url-builder-introducing-the-shortcodes Introducing the first release of shortcodes for Campaign URL Builder plugin. Shortcodes can be customized with various attributes and can generate forms on the front-end. Bitly API key is required. Shortcodes can be styled and have optional parameters. Examples provided for simple, advanced, and PHP usage. Today, I’m excited to introduce the first release of shortcodes for [Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) plugin. Shortcode settings moved to new tab **Shortcode** on top tabs menu. ![Campaign URL Builder Shortcode Settings](/_astro/campaign-url-builder-shortcode-settings.C1VNNVG0_Z27yIWR.webp) By default, shortcodes disabled, also if you use bulletin API key shortcodes also will be disabled. Please use your API key for Bitly. How to find your Bitly API key, [read here](/blog/how-do-i-find-my-bitly-oauth-access-token/). ![Campaign URL Builder Shortcode Settings API Key](/_astro/campaign-url-builder-introducing-the-shortcodes.DUIxHK11_4cd78.webp) _This is how default shortcode generate a form on front-end_ ## How to use shortcode 1. Setup your [Bitly API Key](/blog/how-do-i-find-my-bitly-oauth-access-token/). 2. Enable Shotcode toggle in plugin settings. 3. (optional) Enable shortcode for Anonymous visitor. 4. Place shortcode (Campaign-URL-Builder) on target page. ### Styling By default, shortcode styles have very basic styles, you can disable it and create your styles. ### Shortcode attributes Attributes is optional and for your choice. - wrapper=”anyCssClass additionalClass” - wrapper-inline-style=”background: #ff0000; padding: 10px” - form=”anyCssClass additionalClass” - form-inline-style=”background: #ffff00; padding: 15px” - input-class=”anyCssClass additionalClass” - type=”preset” - campaign\_page=”[https://alex.zappa.dev/wp-plugin-campaign-url-builder/”](https://alex.zappa.dev/wp-plugin-campaign-url-builder/%E2%80%9D) - utm\_source=”reatlat\_homepage” - utm\_medium=”blogpost” - utm\_campaign=”Introducing shortcodes” - utm\_term=”serach key words” - utm\_content=”some content” - custom\_parameters=”key=value|key2=value|key3=value3″ - hidden=”utm\_source,utm\_medium,utm\_campaign” This is stock example, you can replace it like you want. ### Important to know. If you use `type=”preset”` all fields which already have presented attributes will have read only attribute, and front end users can’t replace it. The `custom_parameters`, you can pass only 3 couples. For example `my_param=someValue`. please separate couples by Vertical Bar. The attribute `hidden` can contain the name of fields which you want to hide from front-end. For example, `hidden=”utm_source,utm_medium,utm_campaign”` in this case 3 input fields will be hidden for users. Very soon, I will prepare the visual shortcode generator. ### Examples Simple example ``` [Campaign-URL-Builder] ``` Advanced example ``` [Campaign-URL-Builder wrapper="MegaWrapperClass" wrapper-inline-style="background:#ccc;padding:15px;" form="MegaFormClass" form-inline-style="padding:10px; background:#c19393" campaign_page="https://alex.zappa.dev" utm_source="" utm_medium="" utm_campaign="" utm_term="" utm_content=""] ``` PHP example ``` ``` May the 4th be with you, Alex - [PreviousTransitioning Google URL shortener to Bitly](/blog/transitioning-google-url-shortener-to-bitly/) - [NextHow do I find my Bitly OAuth access token?](/blog/how-do-i-find-my-bitly-oauth-access-token/) --- ## Claude Code signs every commit you make. Here's how to stop it. URL: https://alex.zappa.dev/blog/claude-code-coauthored-commits Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. Open any repo where you’ve used Claude Code and run `git log`. You’ll see the same thing on every commit: ``` Co-Authored-By: Claude ``` It’s on by default. No opt-in. No prompt. Claude writes some code, you review it, you approve it, and Anthropic’s name lands in your git history forever. I noticed this a while back and mostly shrugged. Then Anthropic announced watermarking for Claude-generated code, pitched as “responsible AI.” And I realized they’ve been shipping a different watermark for months. The one that puts their brand on your commits. ## AI can’t own copyright Let’s get the legal part out of the way, because it matters. The U.S. Copyright Office has been clear: works generated entirely by AI have no human authorship and get no copyright protection. That goes back to the Thaler case and the _Zarya of the Dawn_ registration, where the human had to prove meaningful creative input before the Office would register anything. What that means here: the `Co-Authored-By: Claude` trailer grants Claude nothing. It can’t hold copyright. It can’t be a co-owner of the code. It’s legally empty. So if the trailer gives Claude no rights, what is it for? ## The trailer isn’t legal protection. It’s a land grab. Think about what the trailer actually does. Every company that adopts Claude Code gets Anthropic’s brand stamped into their commit history. Every repo. Every developer. Forever. That’s not a copyright claim. That’s an adoption metric wearing a commit trailer as a costume. What does Anthropic do with it? Train on the commit data? Count “millions of Claude-assisted commits” in their next funding deck? I don’t know. They haven’t said. But the design tells you everything you need to know: they made it opt-out instead of opt-in. If it were genuinely about giving credit where credit is due, it would be off by default and you’d turn it on. The fact that you have to discover a hidden config file to turn it _off_ means the default exists for Anthropic’s benefit, not yours. ## How to turn it off Ten seconds. Open `~/.claude/settings.json` and add an `attribution` block: ``` { "attribution": { "commit": "", "pr": "" } } ``` Empty strings disable the attribution on both commits and pull requests. Your commits are yours again. If you’d rather keep a marker but control the wording, you can set it to whatever you want: ``` { "attribution": { "commit": "đŸ€– Generated with Claude Code", "pr": "đŸ€– Generated with Claude Code" } } ``` That’s your call. The point is it should be your call, not a default someone else picked for you. ## And it doesn’t even work reliably Here’s the part that really sells it. The opt-out is documented, but there’s a [long-standing GitHub issue](https://github.com/anthropics/claude-code/issues/45137) where `attribution.commit: ""` doesn’t actually stop the trailer from appearing. Reddit has the same threads. People set the config, commit, and still see Claude’s name in their history. So you have a feature that’s on by default, gives the AI zero legal rights, exists for someone else’s benefit, and the off switch is buggy. That’s a pattern, not a bug report. ## Your repo, your name I’m not anti-AI. I use these tools daily. What I object to is a default that quietly puts someone else’s name on my work, in my history, across every project I touch. If a company ships a feature that stamps their brand on your work by default, that feature isn’t for you. It’s for them. And if you’re thinking “it’s just a commit message, who cares” — then it shouldn’t matter if I turn it off. Right? May the 4th be with you, Alex - [PreviousAstro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) - [NextI walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) --- ## Migrating from Cloudflare Pages to Workers (and cleaning up the mess) URL: https://alex.zappa.dev/blog/cloudflare-pages-to-workers-migration My experience merging an 11ty static site and API into a single Cloudflare Worker, plus a bash script to delete old Pages deployments when the dashboard won't let you. Cloudflare is [deprecating Pages](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/) in favor of Workers with Static Assets. It’s not a sudden death, but the writing is on the wall. Time to migrate. I had a setup with two separate projects: an 11ty static site on Pages and an API on Workers. Decided to merge them into one Worker project that handles both static assets and API routes. Clean and simple. The migration itself was surprisingly smooth. Cloudflare’s docs are solid, and there are some good community guides out there: - [Official Cloudflare migration guide](https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/) - [Vibe Coding with Fred: Pages to Workers Migration](https://vibecodingwithfred.com/blog/pages-to-workers-migration/) - [Ben White: Pages to Workers](https://benwhite.com.au/blog/pages-to-workers/) ## The new setup Here’s what a combined Worker + Static Assets config looks like: ``` { "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-website", "main": "worker/index.js", "compatibility_date": "2025-11-17", // Disable workers.dev subdomain (using custom domain only) "workers_dev": false, "preview_urls": false, // Static assets configuration // Static assets are served first EXCEPT for /api/* patterns "assets": { "directory": "./_site", "binding": "ASSETS", "not_found_handling": "404-page", "run_worker_first": ["/api/*"] }, // Custom domain routing "routes": [ { "pattern": "example.com", "custom_domain": true } ], // Environment variables are stored in: // - .dev.vars (local development, gitignored) // - Cloudflare dashboard secrets (production) "observability": { "enabled": true, "head_sampling_rate": 1 }, // KV Namespaces "kv_namespaces": [ { "binding": "CACHE_KV", "id": "your-kv-namespace-id" } ], // D1 Database "d1_databases": [ { "binding": "DB", "database_name": "my_database", "database_id": "your-d1-database-id" } ] } ``` The key is `run_worker_first` — it routes API requests through the Worker while serving static files directly. Best of both worlds. ## The unexpected problem Everything worked great. But when I tried to delete the old Pages project
 nope. đŸ˜€ > “We are very sorry that you did not succeed.” > — Cloudflare, when you try to delete a Pages project with 500+ deployments ![King's Quest Game Over screen - We are very sorry that you did not succeed](/_astro/kings-quest-game-over.DZC1ozzN_2cPLvB.webp) This weekend I was playing the original King’s Quest on my Steam Deck (bought the whole Sierra collection of 7 games on sale), and this game over message felt way too familiar. The game brings back warm memories of childhood when our whole family would sit together and play this wonderful adventure. If I remember correctly, it was even translated into Russian back in the day. The dashboard just wouldn’t let me. Too many deployments. Turns out, Cloudflare has a “protection mechanism” to prevent accidental deletion of projects with a high number of deployments. Which is
 fine, I guess? But also annoying when you actually want to delete something. ## The official solution Cloudflare documents this issue in their [known issues page](https://developers.cloudflare.com/pages/platform/known-issues/#delete-a-project-with-a-high-number-of-deployments). Their solution? Download a [Node.js script](https://pub-505c82ba1c844ba788b97b1ed9415e75.r2.dev/delete-all-deployments.zip), run `npm install`, set environment variables, and execute it. It works, but requires Node.js dependencies (`node-fetch`, `exponential-backoff`) and uses environment variables which end up in your shell history. I wanted something simpler. I also found [this StackOverflow question](https://stackoverflow.com/questions/79606336/mass-delete-cloudflare-pages-deployments) where others had the same problem. ## My solution: a simple bash script I wrote a quick bash helper that does the same thing. No npm, no extra dependencies. Just `curl`, `jq`, and a few minutes of your time. ``` #!/bin/bash # Delete Cloudflare Pages/Workers deployments via API # Usage: ./delete-cf-deployments.sh --project=xxx --account=xxx --token=xxx [--limit=N] [--keep=N] PROJECT="" ACCOUNT_ID="" TOKEN="" LIMIT="" KEEP="" for arg in "$@"; do case $arg in --project=*) PROJECT="${arg#*=}" ;; --account=*) ACCOUNT_ID="${arg#*=}" ;; --token=*) TOKEN="${arg#*=}" ;; --limit=*) LIMIT="${arg#*=}" ;; --keep=*) KEEP="${arg#*=}" ;; esac done if [ -z "$PROJECT" ] || [ -z "$ACCOUNT_ID" ] || [ -z "$TOKEN" ]; then echo "Usage: $0 --project= --account= --token= [options]" echo "" echo "Required:" echo " --project=NAME Cloudflare Pages project name" echo " --account=ID Your Cloudflare account ID" echo " --token=TOKEN Cloudflare API token" echo "" echo "Options:" echo " --limit=N Delete only N deployments (for testing)" echo " --keep=N Keep the last N deployments (cleanup mode)" echo "" echo "Examples:" echo " $0 --project=my-site --account=abc123 --token=xyz789 --limit=3" echo " $0 --project=my-site --account=abc123 --token=xyz789 --keep=10" echo " $0 --project=my-site --account=abc123 --token=xyz789" echo "" echo "Get API token: Cloudflare Dashboard → My Profile → API Tokens" echo " → Create Token → Edit Cloudflare Pages" echo "" echo "Get Account ID: Cloudflare Dashboard → any site → Overview" echo " → right sidebar under 'API'" exit 1 fi API="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT" echo "Deleting deployments for: $PROJECT" [ -n "$LIMIT" ] && echo "Limit: $LIMIT" [ -n "$KEEP" ] && echo "Keeping last: $KEEP deployments" echo "" COUNT=0 PAGE=1 # For --keep mode, we need to skip the first N deployments # API returns deployments sorted by date (newest first) # So we paginate past the ones we want to keep if [ -n "$KEEP" ]; then # Calculate which page to start from PER_PAGE=25 SKIP_PAGES=$((KEEP / PER_PAGE)) SKIP_IN_PAGE=$((KEEP % PER_PAGE)) PAGE=$((SKIP_PAGES + 1)) echo "Skipping first $KEEP deployments (starting from page $PAGE)..." echo "" fi while true; do # Fetch deployments with pagination RESPONSE=$(curl -s "$API/deployments?per_page=25&page=$PAGE" \ -H "Authorization: Bearer $TOKEN") IDS=$(echo "$RESPONSE" | jq -r '.result[].id // empty' 2>/dev/null) if [ -z "$IDS" ]; then echo "No more deployments." break fi # Convert to array IDS_ARRAY=($IDS) START_INDEX=0 # On the first page in --keep mode, skip partial entries if [ -n "$KEEP" ] && [ "$PAGE" -eq "$((SKIP_PAGES + 1))" ] && [ "$SKIP_IN_PAGE" -gt 0 ]; then START_INDEX=$SKIP_IN_PAGE echo "Skipping first $SKIP_IN_PAGE on this page..." fi for ((i=START_INDEX; i<${#IDS_ARRAY[@]}; i++)); do ID="${IDS_ARRAY[$i]}" echo "Deleting $ID..." curl -s -X DELETE "$API/deployments/$ID?force=true" \ -H "Authorization: Bearer $TOKEN" > /dev/null COUNT=$((COUNT + 1)) if [ -n "$LIMIT" ] && [ "$COUNT" -ge "$LIMIT" ]; then echo "" echo "Reached limit of $LIMIT. Deleted $COUNT deployments." exit 0 fi sleep 0.3 done # In normal mode (no --keep), always fetch page 1 since we're deleting # In --keep mode, move to next page if [ -z "$KEEP" ]; then PAGE=1 else PAGE=$((PAGE + 1)) fi done echo "" echo "Done! Deleted $COUNT deployments." echo "" echo "To delete the project entirely, run:" echo " npx wrangler pages project delete $PROJECT" echo "" echo "Or delete it via Cloudflare Dashboard:" echo " Workers & Pages → $PROJECT → Settings → Delete project" ``` ## How it works The script loops through all deployments using the Cloudflare API, deleting them one by one with a small delay to avoid rate limiting. Once done, it prints instructions for deleting the project via Wrangler or Dashboard. ### Features - **`--limit=N`** - Test mode. Delete only N deployments to make sure everything works before going all in. - **`--keep=N`** - Cleanup mode. Keep the last N deployments and delete the rest. Perfect for regular maintenance. - **No dependencies** - Just bash, curl, and jq. That’s it. ## Getting your credentials You’ll need two things: 1. **API Token** - Go to Cloudflare Dashboard → My Profile → API Tokens → Create Token → “Edit Cloudflare Pages” template. 2. **Account ID** - Open any site in Cloudflare Dashboard → Overview → look at the right sidebar under “API”. ## Example output ``` Deleting deployments for: my-old-project Limit: 10 Deleting a1b2c3d4-e5f6-7890-abcd-ef1234567890... Deleting b2c3d4e5-f6a7-8901-bcde-f12345678901... Deleting c3d4e5f6-a7b8-9012-cdef-123456789012... Deleting d4e5f6a7-b8c9-0123-defa-234567890123... Deleting e5f6a7b8-c9d0-1234-efab-345678901234... Deleting f6a7b8c9-d0e1-2345-fabc-456789012345... Deleting a7b8c9d0-e1f2-3456-abcd-567890123456... Deleting b8c9d0e1-f2a3-4567-bcde-678901234567... Deleting c9d0e1f2-a3b4-5678-cdef-789012345678... Deleting d0e1f2a3-b4c5-6789-defa-890123456789... Reached limit of 10. Deleted 10 deployments. ``` ## Get the script I’ve put the script on GitHub Gist for easy access: 👉 [delete-cf-deployments.sh](https://gist.github.com/reatlat/0caeaa62996758ac57abebf6124de93f#file-delete-cf-deployments-sh) Feel free to use it, fork it, improve it. If you find any bugs or have suggestions, let me know! ## Why not just use the official script? The official solution works fine. But it requires Node.js, `npm install`, and passes credentials via environment variables (which end up in shell history). My bash script: - Has zero dependencies beyond `curl` and `jq` - Passes credentials as arguments (not in env vars) - Adds `--keep=N` option to retain recent deployments - Works for both Pages and Workers projects If you prefer Node.js, use theirs. If you want something lighter, use mine. đŸ€· ## TL;DR: Cloudflare won’t let you delete Pages/Workers projects with too many deployments. Their solution needs Node.js. Mine is just bash. [Grab the script](https://gist.github.com/reatlat/0caeaa62996758ac57abebf6124de93f) and clean up your old deployments. May the 4th be with you, Alex - [PreviousHow I replaced my ISP router and extracted PPPoE credentials using a bridge sniffer](/blog/isp-router-replacement/) - [NextHubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) --- ## Custom user avatars for WordPress URL: https://alex.zappa.dev/blog/custom-user-avatars-for-wordpress Adding custom avatar functionality to your WordPress site. Simple and quick solutions to make your website even more attractive to users! WordPress is a powerful platform that allows you to create and manage a wide variety of websites. One of the key features of WordPress is its user management system, which allows you to create user accounts and assign different roles and permissions to each user. By default, WordPress uses Gravatar to display user avatars, but you may want to add custom avatar functionality to your site to make it more attractive to users, or maybe you want to give users the option to upload their own avatar images. Or maybe the author no have access to Gravatar anymore
 In this blog post, I want to share with you a simple and quick solution to add custom avatar functionality to your WordPress. ## Advanced custom fields To add custom avatar functionality to your WordPress site, we will use the Advanced Custom Fields (ACF) plugin. ACF is a powerful plugin that allows you to create custom fields for posts, pages, and users. First, you need to install and activate the Advanced Custom Fields plugin on your WordPress site. Once you have installed the plugin, you can add a code snippet to your theme’s `functions.php` file to create a custom field for user avatars. See the code snippet below: ``` if (class_exists('ACF')) { add_action('acf/include_fields', function () { if (!function_exists('acf_add_local_field_group')) { return; } acf_add_local_field_group(array( 'key' => 'user_extra_options', 'title' => 'User Extra Options', 'fields' => array( array( 'key' => 'user_extra_options__custom_gravatar', 'label' => 'Custom Gravatar', 'name' => 'custom_gravatar', 'aria-label' => '', 'type' => 'image', 'instructions' => '', 'required' => 0, 'conditional_logic' => 0, 'wrapper' => array( 'width' => '', 'class' => '', 'id' => '', ), 'return_format' => 'id', 'library' => 'all', 'min_width' => '', 'min_height' => '', 'min_size' => '', 'max_width' => '', 'max_height' => '', 'max_size' => '', 'mime_types' => '', 'preview_size' => 'thumbnail', ), ), 'location' => array( array( array( 'param' => 'user_form', 'operator' => '==', 'value' => 'all', ), ), ), 'menu_order' => 0, 'position' => 'normal', 'style' => 'default', 'label_placement' => 'top', 'instruction_placement' => 'label', 'hide_on_screen' => '', 'active' => true, 'description' => '', 'show_in_rest' => 0, )); }); function sunnypixels_custom_avatar($avatar, $id_or_email, $size, $default, $alt, $args) { $id = sunnypixels_get_user_id($id_or_email); if (!$id) return $avatar; $custom_avatar = get_user_meta($id, 'custom_gravatar', true); if ($custom_avatar) { $custom_avatar = wp_get_attachment_image_src($custom_avatar)[0]; $avatar = "{$alt}"; } return $avatar; } function sunnypixels_custom_avatar_url($url, $id_or_email, $args) { $id = sunnypixels_get_user_id($id_or_email); if (!$id) return $url; $custom_avatar = get_user_meta($id, 'custom_gravatar', true); if ($custom_avatar) { $custom_avatar = wp_get_attachment_image_src($custom_avatar)[0]; $url = $custom_avatar; } return $url; } function sunnypixels_get_user_id($id_or_email) { if (is_numeric($id_or_email)) return $id_or_email; if (is_object($id_or_email)) return $id_or_email->user_id; if (is_string($id_or_email)) return get_user_by('email', $id_or_email) ? get_user_by('email', $id_or_email)->ID : 0; return 0; } add_filter('get_avatar', 'sunnypixels_custom_avatar', 100500, 6); add_filter('get_avatar_url', 'sunnypixels_custom_avatar_url', 100500, 3); } else { add_action('admin_notices', function () { echo '

Advanced Custom Fields plugin is required for Custom Gravatar functionality.

'; }); } ``` You can find copy of [this snippet here](https://gist.github.com/reatlat/a84798425b45ad4f18f2b5be9e13a3ff). The result of this code snippet is a new field in the user profile page, where users can upload their custom avatar images. The custom avatar images will be displayed instead of the Gravatar images on the site. ![Custom Gravatar Field in User Profile](/_astro/wp-custom-gravatar-dashboard.g31BI-n9_Z1Cb4g9.webp) ## Alternative solutions if you don’t want to edit your WordPress theme, you can use a plugin variation of this solution, I coded for you. You can find it [here](https://github.com/sunnypixels-io/wp-custom-gravatar). I hope this blog post helps you add custom avatar functionality to your WordPress site. If you like this plugin, please consider to give it a star on GitHub. May the 4th be with you, Alex - [PreviousHow to use structured data (schema markup) in HubSpot to improve SEO](/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo/) - [NextUpgrading to Eleventy v3](/blog/upgrading-to-eleventy-v3/) --- ## Detect dark or light system mode URL: https://alex.zappa.dev/blog/detect-dark-or-light-system-mode Learn how to use prefers-color-scheme CSS media feature to detect user color theme preferences. See examples using media query and JavaScript. Also, track color mode with Google Tag Manager and Google Analytics. As developer, I do prefer dark schema for system and all my apps except Gmail on mobile 🙂 Emails still not so friendly for dark theme mode 🙂 ![Light IDE Meme](/_astro/meme-light-ide.C503GgSB_I7LUR.webp) ## Detect with CSS media query The prefers-color-scheme CSS media feature is used to detect if the user has requested a light or dark color theme. > The user might indicate this preference through an operating system setting (e.g. light or dark mode) or a user agent setting. > > [From MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) ### prefers-color-scheme: dark Indicates that a user has notified the system that they prefer an interface that has a dark theme. When you want a style that applies to users with dark mode turned on, add your style inside media query below. If we want the black background and white text for dark mode users, we will type something like this: ``` @media (prefers-color-scheme: dark) { body { background-color: #000000; color: #FFFFFF; } } ``` ### prefers-color-scheme: light Indicates that the user has notified the system that they prefer an interface that has a light theme. When you want a style that applies to users with light mode turned on, add your style inside media query below. If we want the white background and black text for light mode users, we will type something like this: ``` @media (prefers-color-scheme: light) { body { background-color: #FFFFFF; color: #000000; } } ``` ## Detect with JS ## window.matchMedia() > The Window interface’s matchMedia() method returns a new MediaQueryList object that can then be used to determine if the document matches the media query string, as well as to monitor the document to detect when it matches (or stops matching) that media query. > > [From MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) Here is simple example how to use it: ``` if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { // dark mode } else { // light mode or not supported } ``` To watch for changes: ``` window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { const newColorScheme = e.matches ? "dark" : "light"; }); ``` ## Detect with GTM and track down with GA Google Tag Manager could detect user’s theme color preferences, and after send that data to Google Analytics for example. ### Define GTM dataLayer variable Open Google Tag Manager Dashboard, on sidebar select tab Variables and add new User-Defined Variables Select Custom JavaScript and copy-paste code from below: ``` function () { if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'Dark'; } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { return 'Light'; } return 'No Preference'; } ``` Result should look like a screenshot. ![Google Tag Manager Custom JavaScript](/_astro/google-tag-manager-custom-javascript.DaQ77Tre_Z2sKVpD.webp) ### Tracking with GA Universal To track user color mode with Google Analytics Universal, we need to create a tag in GTM like screenshot below, and fire it on PageView trigger. ![Google Tag Manager GA Universal Event](/_astro/google-tag-manager-ga-universal-event.CZLkXDJr_wYqXo.webp) ### Tracking with GAv4 Not a secret, Google Analytics v4 has new way to collect data, and in this case, we need to create a tag in GTM and also create custom dimension in Google Analytics. ![Google Tag Manager User Color Mode GAv4](/_astro/google-tag-manager-user-color-mode-gav4.Ba9tYihi_CPOlT.webp) ![Google Analysis v4 Custom Dimension](/_astro/google-analysis-v4-custom-dimension.BaeATG1A_1PkfxJ.webp) After all set, in 48h, you will start to see User color mode in your Google Analytics reports. This is it, hope this article help to learn something new, GTM and GA could help us to collect a lot of different information, to learn how to do that, I strongly recommend checking [Analytics Mania](https://www.analyticsmania.com/) where Julius teaching how to use GTM and GA in god mode 😉 May the 4th be with you, Alex - [PreviousMobile friendly Responsive images with CSS](/blog/mobile-friendly-responsive-images-with-css/) - [NextMy favorite CSS hacks](/blog/my-favorite-css-hacks/) --- ## GitKraken – cross-platform editor for git repos URL: https://alex.zappa.dev/blog/gitkraken-cross-platform-editor-for-git-repos GitKraken is a cross-platform git interface written in electron and open libraries, offering merge conflict tools and a task-tracking system. Get a $20Off discount for a one-year subscription. I did not use the GUI to work with git. When I was just starting to program, there was nothing interesting for Linux, but theoretically, I was immediately taught to the command line, so no interfaces were required. Then [gitg](https://wiki.gnome.org/Apps/Gitg) and IDEA came along with its integration with git, and this perfectly complemented my work on the command line. However, I still follow different projects, especially if they are written on the technologies of the front. So, I stumbled upon GitKraken. Cross-platform git interface. ## GitKraken ![GitKraken Interface](/_astro/girkraken-interface.DkU9sxAo_845f3.webp) GitKraken is written in [electron](https://www.electronjs.org) and uses open libraries: [NodeGit](https://www.nodegit.org), [libgit2](https://libgit2.org). This is exactly what allows it to simply support cross-platform. ![GitKraken Merge](/_astro/gitkraken-merge.-MvG3mbl_24HbY5.webp) A certain minus of the program is that it is limited free(Freemium). For personal use, with some functional limitations, it is completely free, but for professional work you have to take subscription. The cost of a subscription per year is not costly, just $49 per year. Recently found an excellent coupon with a $20Off discount for a one-year subscription. Coupon can be obtained if you follow the link [https://www.gitkraken.com/switch](https://www.gitkraken.com/switch), GitKraken thus attracts new subscribers. > By the way, you can immediately use the **switch2018** coupon. ### GitKraken Pro – Features ![GitKraken Pro Features](/_astro/gk-features.B3avxpDI_1SFD0X.webp) There are many goodies, but the most beautiful thing, in my opinion, is that the merge conflict tools, merge a conflicting code very difficult on big projects, and often took a lot of time because something could be overwritten and a whole code branch was lost. GitKraken Tutorial: Merge Conflict Tool With GitKraken, this has become elementary, the code highlighting system for changes in the code (DIFF) and the built-in editor allow you to make changes to the files on the fly and the build process becomes effortless. This and many other pleasant things make working with Git using GitKraken very fast and comfortable, and therefore increases productivity. GitKraken also recently introduced a task-tracking system similar to Trello, called GitKraken Glo. Together with the GitHub Issues, the Glo can be an excellent task manager. About GitKraken Glo will write a little later. Have fun? May the 4th be with you, Alex - [PreviousAdding subscribers to a list using Mailchimp’s API v3](/blog/adding-subscribers-to-a-list-using-mailchimps-api-v3/) - [NextWP plugin Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) --- ## How do I find my Bitly OAuth access token? URL: https://alex.zappa.dev/blog/how-do-i-find-my-bitly-oauth-access-token Learn how to sign up for a Bitly account and generate an access token for the Bitly API. Follow these steps to get started. Do you have Bitly account? If not [signup](https://bitly.com/a/sign_up) right now. If you are looking for start using the Bitly API, you will need your Oauth Access Token. To find your OAuth access token navigate to the hamburger menu button in the top right-hand corner. From the drop down select ‘Settings’ and then continue to the ‘Advanced Settings’ tab. Look to the bottom under ‘For Developers’ and click on the OAuth hyperlink. ![Bitly OAuth Access Token](/_astro/api-key-with-arrow.M9BVK8hd_16zGlu.webp) If you need to generate an access token so you can use our API, you can go to [https://bitly.com/a/oauth\_apps](https://bitly.com/a/oauth_apps), click “Generic Access Token”, ![Bitly Edit Profile Generic Access Token](/_astro/bitly-edit-profile-generic-access-token.D7kleGyG_2iNmjP.webp) Then enter your password to create the token, and then copy that token into your code or configuration. Access tokens don’t expire (though you can expire them manually if yours leaks into the wild), so you can use the single access token as long as you need. May the 4th be with you, Alex - [PreviousCampaign URL Builder Introducing the Shortcodes](/blog/campaign-url-builder-introducing-the-shortcodes/) - [NextHow remove .DS\_Store files from a Git repository?](/blog/how-remove-dsstore-files-from-a-git-repository/) --- ## How remove .DS_Store files from a Git repository? URL: https://alex.zappa.dev/blog/how-remove-dsstore-files-from-a-git-repository Learn how to remove .DS_Store files from your Mac OS X repository. Use commands to find and remove the files. Update .gitignore for future prevention. Check gitignore.io for more configurations. Mac OS X users can include system .DS\_Store files to repository accidentally. First, you can check how much .DS\_Srore files you have on your work directory. Use fallow command: ``` find .* -name ".DS_Store" -type f ``` To remove all .DS\_Store files from git you can use fallowing command: ``` find .* -name .DS_Store -print0 | xargs -0 git rm -f --ignore-unmatch ``` ## Update .gitignore file Add to the file `.gitignore`, which can be found at the top level of your repository (or created if it isn’t there already). You can do this easily with this command in the top directory: ``` echo "# Apple macOS\n.DS_Store\n.DS_Store?\n*/.DS_Store" >> .gitignore ``` Then add and commit your new `.gitignore` file ``` git add .gitignore && git commit -m '.DS_Store banished!' ``` ## Avoid this issue in future ``` echo "# Apple macOS\n.DS_Store\n.DS_Store?\n*/.DS_Store" >> ~/.gitignore_global ``` Set the global git configuration ``` git config --global core.excludesfile ~/.gitignore_global ``` Admire your great work? Also, check [gitignore.io](https://www.toptal.com/developers/gitignore/) where you can configure your own `.gitignore` file by few clicks. May the 4th be with you, Alex - [PreviousHow do I find my Bitly OAuth access token?](/blog/how-do-i-find-my-bitly-oauth-access-token/) - [NextHow to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) --- ## How to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu) URL: https://alex.zappa.dev/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu Block visitors by country using NGINX and the GeoIP module. Install the module and database, configure the nginx.conf file, and test the configuration before restarting nginx. Instructions with examples provided. In the last time, I see a lot of attack from botnet with IPs from eastern europe. Several of my projects aren’t concerned about visitors from those countries, so we just block all of them 🙂 ## How block visitors by Country? This tutorial explains how to use the GeoIP module with NGINX to block visitors by country. This is made possible by the GeoIP database which maps users’ IP addresses to countries. NGINX must be version 1.9.11 or higher, and installed the [HttpGeoipModule](http://nginx.org/en/docs/http/ngx_http_geoip_module.html) to use the GeoIP database. Actually, it’s pretty easy with the latest Nginx release. I use nginx v1.15.5, which I think the last stable one 🙂 ## Let’s start. Install GeoIP module and database. We need install 3 packages using this command ``` apt-get install nginx-module-geoip geoip-database libgeoip1 ``` Perhaps the last one package(libgeoip1) you already have on your server 🙂 After that we have to include new module and rules to file ``` /etc/nginx/nginx.conf ``` Add before _http_ block open: ``` [...] # Load GeoIP module load_module "modules/ngx_http_geoip_module.so"; [...] ``` Place next block in the _http_ block, before any _include_ lines: ``` [...] # ISO codes here https://dev.maxmind.com/geoip/legacy/codes/iso3166/ geoip_country /usr/share/GeoIP/GeoIP.dat; map $geoip_country_code $allowed_country { default yes; RU no; UA no; BY no; } [...] ``` Now, this actually doesn’t block any country, it just sets the _$allowed\_country_ variable. To actually block countries, you must open your vhost configuration and place the following code in the _server_ container (this can go inside and also outside any _location_ block): ``` [...] if ($allowed_country = no) { return 444; } [...] ``` Definitely, you can have a bit more complicated config structure, but this is just example how it’s work. We return **Error 444 connections closed without response.** A non-standard status code used to instruct nginx to close the connection without sending a response to the client, most commonly used to deny malicious or malformed requests. This status code is not seen by the client, it only appears in nginx log files. More information on [nginx documentation](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return). ## Test NGINX configuration file Run command ``` nginx -t ``` You have to see this respond ``` nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` If your configuration pass the test, restart nginx using _systemctl_ ``` systemctl restart nginx ``` Admire your great work ? and test how it’s work with VPN service. May the 4th be with you, Alex - [PreviousHow remove .DS\_Store files from a Git repository?](/blog/how-remove-dsstore-files-from-a-git-repository/) - [NextWP plugin Elementor Multidomain Support](/blog/wp-plugin-elementor-multidomain-support/) --- ## How to change App Store preferred Language 2022 URL: https://alex.zappa.dev/blog/how-to-change-app-store-preferred-language-2022 Resolve your AppStore language issue on macOS! Despite numerous solutions, I discovered a simple fix for the problem. Change your VPN to UK location, log out of AppStore, force close it, and open again. After logging in, AppStore will switch to English. My wife has issue with AppStore on MacBook, her account, and iPhone was set up in English but for some reason AppStore on macOS have been in Spanish. ![App Store in Spanish](/_astro/appstore-esp.BECP9yqr_Z2bmX8P.webp) In internet, you can find numerous instructions how to change language of your account and AppStore. Some of them live on Apple Support pages, but no one solution works for me, and according to google requests, looks like I’m not the only one who has this issue. As AppleCare+ holder I decided to ask Apple advisors about my issue by email first, with no good answer 🙁 Later I try to contact Apple Support via chat, and there are a couple of funny stories
 So far, I would like to say, Apple Advisors are the most friendly and skilled support ever! But even they, couldn’t help me 🙁 First I got connected to Super Advisor who tried to help me, but the chat looks like freez.. Or he made it looks like freeze :/ Anyway, second time I got connection with very friendly guy who try to debug a problem and ask me to try alternatives. No one of their solutions don’t want to solve my issue, we have tried to remove VPNs, remove LittleSnitch Firewall, reset settings, check Apple ID, brake SMC settings 🙂 Nothing helps, and he decides to schedule a phone call with super Advisor 🙂 Long story short
 after all manipulations I made, and taking short coffee break, I found a simple solution! The App Store on macOS its an webpage AppView, and language of AppStore based on geoIP location, so if you based in Spain like I am and you log in to appStore, the language will be set to Spanish. Even your account set up to English, so based on my discovery I set my VPN to UK location, logout from AppStore, force close it, and open again, first it was in Spanish again, but after I login to my Apple ID, AppStore become to English!!! ![App Store in English](/_astro/appstore-eng.Bd59PHFF_ZV04Ax.webp) Later I turn VPN off and restart my mac to see if AppStore change language, but its not, from now my AppStore are always in English. This is it, solution was super simple, but nobody, even Apple Support have no idea about this glitch. May the 4th be with you, Alex - [PreviousMy favorite CSS hacks](/blog/my-favorite-css-hacks/) - [NextMigrating from WordPress to 11ty: enhancing website performance and control](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) --- ## How to manage multiple SSH keys for multiple GitHub accounts on one computer URL: https://alex.zappa.dev/blog/how-to-manage-multiple-ssh-keys-for-multiple-github-accounts-on-one-computer Learn how to set up and manage multiple SSH keys on a single computer to seamlessly work with multiple GitHub accounts. Step-by-step guide for developers. Recently [my son](https://zapparov.dev) asked me how to manage multiple SSH keys for multiple GitHub accounts on one computer. He’s starting his journey as a computer science student and needs to work with multiple GitHub accounts for his projects. My first attempt was to find an article or ready solution to point him to. But I couldn’t find a good one. So I decided to write this article to help him and other developers who’re facing the same issue. ![How to Manage Multiple SSH Keys for Multiple GitHub Accounts on One Computer](/_astro/laptop-keys.CR8_Dfwv_ZVjS3r.webp) ## Step-by-step guide ### 1\. Generate two SSH keys If you haven’t already, [generate two SSH keys](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) for each GitHub account: ``` ssh-keygen -t ed25519 -C "personal_email_account@example.com" -f ~/.ssh/id_ed25519_personal ssh-keygen -t ed25519 -C "student_email_account@example.com" -f ~/.ssh/id_ed25519_edu ``` - The `-f` flag specifies the filename for the keys (e.g., id\_ed25519\_personal and id\_ed25519\_edu). > _**If you are using a legacy system that doesn’t support the Ed25519 algorithm, use:**_ > > ``` > ssh-keygen -t rsa -b 4096 -C "personal_email_account@example.com" -f ~/.ssh/id_rsa_personal > ssh-keygen -t rsa -b 4096 -C "student_email_account@example.com" -f ~/.ssh/id_rsa_edu > ``` ### 2\. Add keys to the SSH agent Add both keys to your SSH agent: ``` eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519_personal ssh-add ~/.ssh/id_ed25519_edu ``` ### 3\. Update the SSH config file Edit (or create) your ~/.ssh/config file to define configurations for each GitHub account. ``` # GitHub Personal Account Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_personal IdentitiesOnly yes # GitHub Education Account Host github.edu HostName github.com User git IdentityFile ~/.ssh/id_ed25519_edu IdentitiesOnly yes ``` > _**Why `IdentitiesOnly yes` matters:**_ > > Without it, SSH will offer every key loaded in your `ssh-agent` (and every `IdentityFile` that matches the host) in order. GitHub accepts the **first valid key** it receives — so if your personal key happens to be offered first, you’ll authenticate as your personal account even when connecting via `github.edu`. Setting `IdentitiesOnly yes` tells SSH to use only the key you explicitly specified for that host, which is what you almost certainly want when juggling multiple accounts on the same hostname. ### 4\. Use configured hosts in Git URLs When cloning or working with Git repositories, use the corresponding host name defined in the SSH config: For Personal account: ``` git clone git@github.com:username/repo.git ``` For Education account: ``` git clone git@github.edu:username/repo.git ``` ### 5\. Test your configuration Verify the setup by testing the SSH connection for each account: ``` ssh -T github.com ssh -T github.edu ``` If the setup is correct, GitHub will identify each account and display a success message. ## Tips If you already cloned repositories using the default [git@github.com](mailto:git@github.com) format, update the origin remote URL to use the appropriate host: ``` git remote set-url origin git@github.edu:username/repo.git ``` Make sure the SSH public keys (id\_ed25519\_personal.pub and id\_ed25519\_edu.pub) are added to the respective GitHub accounts in their settings. ### Avoid a catch-all `Host *` with `IdentityFile` A lot of existing SSH configs end with a wildcard block like this: ``` Host * AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/id_ed25519 ``` The problem: `Host *` matches **every** host, including `github.edu`, and `IdentityFile` directives are **cumulative** across matching blocks. Your personal key gets appended to the identity list for every other host, the agent loads it, and it ends up offered first — bypassing the per-host key you carefully set up. Even `IdentitiesOnly yes` on the specific host won’t help, because the wildcard-added key is still considered “explicitly configured.” Either remove the `IdentityFile` line from `Host *` (it’s the default anyway, so you don’t lose anything), or scope the wildcard down to the hosts that actually need it. ### Troubleshooting If `ssh -T github.edu` still greets you with the **wrong** username, check: 1. Run `ssh -v -T github.edu` and look at the `Offering public key:` and `Server accepts key:` lines — they tell you which key actually got used. 2. Run `ssh-add -l` to see what’s loaded in your agent. If the personal key is there, `ssh-add -D` clears everything so only the keys your config points at get offered. 3. Confirm the correct public key is uploaded to the matching GitHub account (a key can only be attached to one account at a time). ## Set up Git global configuration To avoid conflicts, set up a global Git configuration for each account: ``` git config --global user.name "Personal Name" git config --global user.email "personal_email_account@example.com" ``` then create a separate config file for the second account: ``` touch ~/.gitconfig_edu echo "[user]" >> ~/.gitconfig_edu echo " name = Student Name" >> ~/.gitconfig_edu echo " email = student_email_account@example.com" >> ~/.gitconfig_edu ``` and then update the global configuration file `~/.gitconfig` to include the second configuration file: ``` echo "[includeIf \"gitdir:~/path/to/your/education/**\"]" >> ~/.gitconfig echo " path = ~/.gitconfig_edu" >> ~/.gitconfig ``` > _**Note: for Windows users ruleset `includeIf` could be different.**_ > > ``` > echo "[includeIf \"gitdir/i:C:/path/to/your/education/**\"]" >> ~/.gitconfig > echo " path = ~/.gitconfig_edu" >> ~/.gitconfig > ``` This setup allows you to: - Use the correct name and email for each account. - Automatically switch between configurations based on the repository path. - Keep your global configuration clean and organized. ### How it works When you run a Git command in a repository located in the `~/path/to/your/education/` directory, Git will use the configuration from the `~/.gitconfig_edu` file. In all other cases, Git will use the default global configuration. ### Testing the configuration To confirm the correct profile is being applied in a specific directory: ``` git config --get user.name git config --get user.email ``` > _**Important notes:**_ > > - The included files (e.g., `~/.gitconfig_edu`) must be valid Git configuration files. > - Make sure to replace `~/path/to/your/education/**` with the actual path to your education repositories. > - If a directory doesn’t match any includeIf condition, the global configuration will be used. That’s it! You’ve successfully set up and managed multiple SSH keys for multiple GitHub accounts on one computer. May the 4th be with you, Alex - [PreviouswebChronicle: your personal Wayback Machine](/blog/webchronicle/) - [NextGuest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) --- ## How to use structured data (schema markup) in HubSpot to improve SEO URL: https://alex.zappa.dev/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo How to use Structured Data, also referred to as Schema Markup, inside of HubSpot that is populated using HubL variables for use to improve SEO. Structured data, also known as schema markup, is an important tool that can significantly improve your website’s search engine optimization (SEO). By implementing schema markup in HubSpot, you can provide search engines with valuable information about your content, helping them to better understand and display your website in search results. In this blog post, we will explore the benefits of using structured data and provide you a code snippets which you can use to implement schema markup in HubSpot. ## What is structured data? There are many different types of schemas available on [Schema.org](https://schema.org/), with over 760 listed. Schema.org is a website created by Bing, Google, and Yahoo to provide a common set of schemas for structured data. It can be overwhelming for first-time readers to decide which schema to use. However, Google provides a helpful breakdown of specific schemas that can be used for their rich results. These schemas are listed on their [search gallery documentation](https://developers.google.com/search/docs/appearance/structured-data/search-gallery). In this case, we will focus on the Article structured data type, which is commonly used for news, sports, or blog articles. It allows for features like a Top stories carousel, headline text, and larger-than-thumbnail images. ## The benefits of using structured data Using structured data in HubSpot offers several advantages for your website’s SEO. First and foremost, it helps search engines understand the semantics of your content, enabling them to display rich snippets in search results. These snippets provide users with more detailed and visually appealing information about your website, increasing the likelihood of attracting clicks. Additionally, structured data helps search engines categorize your content more accurately, improving its visibility to relevant audiences and increasing organic traffic to your website. ## Implementing structured data in HubSpot To start implementing structured data in HubSpot, you first need to identify the content elements that would benefit from schema markup. This could include blog posts, product pages, events, and more. Once you have identified the relevant content, you can proceed with adding schema markup to your HubSpot templates. HubSpot provides a user-friendly interface that allows you to easily edit your templates, insert schema markup, and preview the changes. Be sure to validate your schema markup using [Google’s Structured Data Testing Tool](https://developers.google.com/search/docs/appearance/structured-data) to ensure it is implemented correctly. ### Template vs module implementations for structured data in HubSpot When deciding how to implement your structured data in HubSpot, there are two options to consider. It’s important to choose the option that suits your needs best. #### Option 1: adding code at the template level This option involves adding the code at the template level. It works well when you have a single schema that applies consistently to your content. For example, if you have a blog that only contains news articles, you can simply include the Article schema in the template. In this case, your content editor doesn’t need to worry about adding any additional information. #### Option 2: creating a module The second option is to create a custom module, either Local or Global, to use on pages or blogs. This method is ideal when you have multiple schema types (such as Article, Event, JobPosting, etc.) that you want to implement within your blog. You can offer options for the content editor to choose from when publishing each piece of content. ## Let’s do some coding BlogPosting schema example: ``` ``` BreadcrumbList schema example: ``` ``` ## Tools to help you implement structured data in HubSpot There are several tools available to help you implement structured data in HubSpot. One of the most popular tools is: - [Schema App](https://www.schemaapp.com/), which offers a free trial and paid plans. Schema App allows you to easily - [JSON-LD](https://jsonld.com/), which is a free tool that allows you to generate JSON-LD code for your website. It also provides a helpful guide on how to implement structured data in HubSpot. - [Google’s Structured Data Testing Tool](https://developers.google.com/search/docs/appearance/structured-data), which allows you to validate your schema markup and preview how it will appear in search results. - [Google’s Rich Results Test](https://search.google.com/test/rich-results), which allows you to test your schema markup and preview how it will appear in search results. - [Google’s Search Gallery](https://developers.google.com/search/docs/appearance/structured-data/search-gallery), which provides a list of schemas that can be used to enhance your website’s search results. - [Google’s Structured Data Markup Helper](https://www.google.com/webmasters/markup-helper/), which allows you to generate structured data markup for your website. - [TechnicalSEO.com’s Schema Markup Generator](https://technicalseo.com/tools/schema-markup-generator/), which allows you to generate structured data markup for your website. - [Schema.org](https://schema.org/), which provides a list of schemas that can be used to enhance your website’s search results. ## Conclusion In conclusion, incorporating structured data in HubSpot is a powerful tactic to enhance your website’s SEO. By providing search engines with more information about your content, you can improve its visibility, attract more organic traffic, and enhance the user experience. Don’t overlook the importance of schema markup in your SEO strategy. Implement it in your HubSpot templates today and reap the benefits of higher search rankings and increased website traffic. Stay Ahead of the competition and make the most out of your HubSpot platform by leveraging the power of structured data. ## Updated (January 7, 2024) I just found out that HubSpot has released [Structured Data (Beta)](https://community.hubspot.com/t5/Releases-and-Updates/Now-live-Setting-to-apply-BlogPosting-JSON-LD-schema-to-blog/ba-p/645867) and it works really well. I’m still think homebrewed solution is better, but it’s good to know that HubSpot is moving in the right direction. May the 4th be with you, Alex - [PreviousThe best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) - [NextCustom user avatars for WordPress](/blog/custom-user-avatars-for-wordpress/) --- ## HubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care URL: https://alex.zappa.dev/blog/hubspot-cookie-banner-google-fonts-gdpr HubSpot's cookie consent banner secretly loads Google Fonts before you even consent, leaking your IP to Google. Here's how I fixed it with a JavaScript proxy hack. You know what’s ironic? A cookie consent banner that violates privacy _before_ you even click “Accept.” That’s exactly what HubSpot’s cookie banner does. It loads Google Fonts, sending your visitor’s IP address to Google’s servers, the moment the page loads. No consent asked. No opt-in. Just a quiet little request to `fonts.googleapis.com` that most people never notice. I noticed. And it took me down a rabbit hole. ## The Problem If you’re using HubSpot’s embedded tracking script on a non-HubSpot website (which many businesses do for CRM, forms, and analytics), you’re also getting their cookie consent banner. Sounds great, right? GDPR compliance out of the box! Except the banner itself loads Google Fonts. Before the visitor has given any consent. Before any cookies are set. The very tool meant to protect privacy is leaking personal data to a third party. Here’s what happens when HubSpot’s script loads: 1. The `hs-script-loader` JavaScript file is fetched 2. HubSpot injects a cookie consent banner into the DOM 3. As part of rendering the banner, it creates `` elements pointing to `fonts.googleapis.com` 4. Google receives the visitor’s IP address, browser info, and referrer 5. The visitor hasn’t consented to anything yet This is not a theoretical problem. In January 2022, the [Munich Regional Court (LG MĂŒnchen)](https://thehackernews.com/2022/01/german-court-rules-websites-embedding.html) ruled that dynamically loading Google Fonts violates GDPR because it transmits the user’s IP address to Google without consent. The website operator was fined €100 in damages, and that ruling triggered a [wave of warning letters across Germany and Austria](https://inplp.com/latest-news/article/the-year-of-google-fonts-warning-letters/). On top of that, HubSpot injects its own ` ``` This is it, it should resolve the issue with oversized images. Here is example on CodePen, how its work: This is it! Hope my post have been helpful, stay safe! May the 4th be with you, Alex - [PreviousKeep Track UTM parameters for HubSpot forms like Cookie Monster](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/) - [NextDetect dark or light system mode](/blog/detect-dark-or-light-system-mode/) --- ## The models were never the magic URL: https://alex.zappa.dev/blog/models-were-never-the-magic Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. Mathias Biilmann wrote this week that [the price of intelligence finally matters](https://biilmann.blog/articles/does-the-price-of-intelligence-matter/). The opener is the most honest thing a CEO has written about models all year: for two years he liked the idea of open weight models without wanting to use one, because cheap was their pitch and cheap was never his problem. Intelligence was. He even let DeepSeek V4 Flash typeset the article and Kimi K3 design the cover. On-message doesn’t cover it. “I’ve spent two years being right that price didn’t matter,” he says, “but I’m betting that won’t hold for another year.” He’s right about the split. One race stays at the frontier, where labs fight over state of the art and nobody shops on price. Another race makes intelligence ubiquitous and almost free. Open weights win the second one. But I’d push it further. Biilmann says DeepSeek V4 Flash is the first model that’s actually good enough and cheap enough to change which problems get AI pointed at them. I say the model stopped being the variable entirely. For most real work, they’re all the same. The difference was never the model. ## The sequel My last post ended on a spoiler: the browser and the IDE were only half the migration. [The AI agent itself](/blog/tool-migration-wavebox-jetbrains/) was a separate story, coming later. This is that story. June 2026 was the point of no return. Fable 5 launched fast on June 9, the US put export controls on it June 12, and Anthropic suspended access globally that same day. The next day I started testing every open model I could find, DeepSeek V4 included. Not out of enthusiasm. Arithmetic: today it’s Fable 5, tomorrow it’s every US model under export control, and my whole pipeline was standing on one of them. I already wrote about the slowdowns before the launch, the week-by-week crawl of Opus 4.8 that magically cleared up once Fable 5 shipped. What I didn’t write about is what I did after. I stopped renting my pipeline. A directive in Washington had just proved it could switch my delivery rhythm off in an afternoon. Fable 5 came back on July 1 once the controls lifted. The lesson didn’t. Whatever came next had to let me swap models without touching my workflow, and never again leave me holding one vendor’s mood. It’s not that I can’t work without them. LLMs are sugar: I’d survive cutting them out, but nobody should quit cold turkey on someone else’s schedule. Better to pick your dependency while the choice is still yours. ## The experiment So the harness changed. I run [Hermes](https://hermes-agent.nousresearch.com) as my main harness now, and the reason has nothing to do with any single model. It’s the plumbing: one harness, any model, tools and MCP servers wired once. That made the experiment easy to run. Local first. qwen3.6 for a while, then qwen3.8 27B when it dropped. Gemma for lighter jobs. Community fine-tunes too, including one where somebody literally trained a model and named it Fable 5. It works fine. The joke writes itself. Then the big open weights through APIs: DeepSeek V4 Flash, GLM 5.2, Kimi K2.7, which is fantastic for code, the fresh Kimi K3, Mistral, Devstral. Swap any of them mid-task and the work still gets done. Coding, PR reviews, changelogs, docs. All fine. All the same. ## It was never the model Here’s the part nobody selling you tokens wants to hear: Gemini, Grok, OpenAI, Claude. Point them all at the same task from the same harness and you get the same job done. I mean the same finished work, not the same tokens. The difference is in the harness. The tools the model can reach. The context you feed it. The memory, the retrieval, the MCP servers. A great model in a bare harness loses to a decent model with real context, every time. That stopped being a thesis months ago. It’s evidence now. One carve-out for research: Gemini and Grok are still the best tools for finding what just happened, tweets, Reddit threads, official sources, fresh stuff. Everything else is interchangeable for code and generic work. ## The Claude Code test I know people who won’t leave Claude Code because it’s the harness they trust. Fine. Keep it. But point the smartest open model at it and tell me what you lose. Install Ollama, take their subscription, connect a custom model from their servers into Claude Code. What happens: zero difference in coding quality, noticeably faster token generation, because their GPUs are faster than Anthropic’s. I have no idea how that economics works. I just watched it happen. The GDPR objection is the usual one. They host compute primarily in the US and route to Europe and Singapore for extra capacity, which settles the legal question for most teams. But honestly, if data residency is your real fear, stop sending your repo through someone else’s cloud at all. Run it local. That answer has been sitting there the whole time. ## The rebellion The open source mood has said the same thing for years. Be local. Own your software, own your hardware. Don’t be somebody’s subscription unit. It used to sound ideological. June 2026 made it engineering. I noticed the speed trick before I had the politics worked out. Opus 4.8 got slower every single week, then Fable 5 arrived fast and fresh, the same speed Opus had back when it was the new thing. A model you rent can slow down whenever the vendor needs you to notice the newer one. A model you run doesn’t negotiate. Biilmann is right about the split, and I’ll add the part he left out: the ubiquity race eats the middle of his chart faster than the labs expect, because the models under the frontier are already good enough. Europe especially lost the luxury of choosing. When one afternoon in Washington can switch off your pipeline, open weights stop being an ideology and become the audit trail. The models were never the magic. The harness was. Own it. May the 4th be with you, Alex - [PreviousI walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) --- ## My favorite CSS hacks URL: https://alex.zappa.dev/blog/my-favorite-css-hacks Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. As it is with every coding language, there are several shortcuts or hacks with CSS that allow you to write cleaner code, improve design elements, and save valuable time. Furthermore, you can directly insert these snippets to your site using a code editor. And here is my favorite CSS Hacks. ## Inherit box sizing The border-box tells the browser to account for any border and padding in the values you specify for an element’s width and height. ``` html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } ``` Your browser does not support the video tag. [MDN CSS Demo – box-sizing](https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing) ## Selection pseudo class Use selection pseudo-class to give a personal touch to text selection on your websites. ## Input Caret Color Set the color of the cursor in input elements. ``` input, textarea { caret-color: #ff0000; } ``` ## No value, No problem Display links when the element has no text value, but the attribute has a link. ``` a[href^="http"]:empty::before { content: attr(href); } ``` ## Fit images to the content In this simple hack, you can make sure that your images always fit the visitor’s screen, regardless of the device they’re using. More use cases could be found [here](/blog/mobile-friendly-responsive-images-with-css/). ``` img { max-width: 100%; height: auto; } ``` ## Detect dark system mode [When you want a style that applies to users with dark mode turned on](/blog/detect-dark-or-light-system-mode/), add your style inside media query below. If we want the black background and white text for dark mode users, we will type something like this: ``` @media (prefers-color-scheme: dark) { body { background-color: #111111; color: #FFFFFF; } } ``` ## Hide images on mobile Sometimes we have icons or images that we just don’t want to be seen on the mobile view. ``` @media only on screen and (max-width: 767px) { section .user-img { display: none; } } ``` ## CSS vendor prefixes by browsers CSS vendor prefixes, also sometimes known as or CSS browser prefixes, are a way for browser makers to add support for new CSS features before those features are fully supported in all browsers. This may be done during a sort of testing and experimentation period where the browser manufacturer is determining exactly how these new CSS features will be implemented. The CSS browser prefixes that you can use (each of which is specific to a different browser) are: - Android: `-webkit-` - Chrome: `-webkit-` - Safari: `-webkit-` - iOS: `-webkit-` - Firefox: `-moz-` - Internet Explorer: `-ms-` - Opera: `-o-` ### Autoprefixer > The Autoprefixer uses data on the popularity of browsers and support for vendor prefixes by browsers. Based on this information, it arranges and deletes the prefixes. It can help you get prefixes for: animations, transition, transform, grid, flex, flexbox and others. > > [https://autoprefixer.github.io/](https://autoprefixer.github.io/) To make sure your CSS works well on the rest of browsers, always use Autoprefixer, here ho is it looks like: ![Autoprefixer CSS online](/_astro/autoprefixer-css-online.BnoIHyNR_Z2cs5e3.webp) ## Conclusion There are countless tricks we can use with CSS3. These hacks have saved me hours of work and made my stylesheets cleaner — I hope they do the same for you. May the 4th be with you, Alex - [PreviousDetect dark or light system mode](/blog/detect-dark-or-light-system-mode/) - [NextHow to change App Store preferred Language 2022](/blog/how-to-change-app-store-preferred-language-2022/) --- ## The best free image placeholder for quick and easy content mockups URL: https://alex.zappa.dev/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. One of the challenges content creators face is finding high-quality, visually appealing images to enhance their articles or blog posts. However, it can be time-consuming and expensive to source these images from stock photo sites. Thankfully, there are free image placeholders available that can quickly and easily serve as temporary replacements until the final images are obtained. In this blog post, we will explore the best free image placeholders to assist content creators in their quest for captivating visual content. ## The Placehold.co experience One popular option for a free image placeholder is [Placehold.co](https://placehold.co/). This platform offers a simple and user-friendly interface that enables users to generate image placeholders of custom sizes simply by modifying the URL. With Placehold.co, content creators can specify the width, height, background color, text color, and even add custom text to their placeholders. This versatility makes it a valuable tool for quick and easy content mockups. See the example below: ``` Placeholder Image ``` ![Placeholder Image](https://placehold.co/320x150?text=320x150%5Cnhttps%3A%2Fplacehold.co%2F) ## Lorem Picsum a.k.a. Unsplash it Another excellent choice for free image placeholders is [Lorem Picsum](https://picsum.photos/)(formerly known as Unsplash It). This platform provides high-quality placeholder images sourced from Unsplash, a popular stock photography platform. Users can specify the size, category, and even apply filters to match their desired aesthetic. These placeholders can be easily downloaded, allowing content creators to populate their drafts with visually pleasing images before acquiring the final ones. See the example below: ``` Placeholder Image ``` ![Placeholder Image](https://unsplash.it/320/150?image=57) ## Unsplash Source API **Updated: Temporary broken or finally deprecated, please check the [Unsplash Source API](https://unsplash.com/developers)** [Unsplash Source](https://unsplash.com/developers) is another free image placeholder that offers a wide selection of placeholder images from [Unsplash](https://unsplash.com/). Users can specify the size, category, and even apply filters to match their desired aesthetic. Additionally, Unsplash Source provides options to customize the background color, text overlay, and even add a logo. With its comprehensive database and customizability, Unsplash Source is a reliable choice for content creators in need of high-quality placeholder images. See the example below: ``` Placeholder Image ``` ## Pixabay fillers [Pixabay Fillers](https://pixabay.com/service/about/api/) is a free image placeholder that offers a wide selection of placeholder images from [Pixabay](https://pixabay.com/). Users can specify the size, category, and even apply filters to match their desired aesthetic. Additionally, Pixabay Fillers provides options to customize the background color, text overlay, and even add a logo. With its comprehensive database and customizability, Pixabay Fillers is a reliable choice for content creators in need of high-quality placeholder images. See the example below: ``` Placeholder Image ``` ![Placeholder Image](https://cdn.pixabay.com/video/2015/08/08/125-135736646_small.jpg) ## Conclusion As content creators, incorporating captivating visuals into our articles and blog posts is crucial for engaging our readers. However, finding the perfect images can be a time-consuming and expensive process. Fortunately, free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers offer convenient solutions for quick and easy content mockups. Whether it’s generating customizable image placeholders, accessing high-quality stock photos, or adding personal touches, these tools provide the necessary resources to enhance our content creation workflow. By utilizing these free image placeholders, content creators can streamline their process and focus on delivering engaging and visually stunning content to their audience. My favorite is [Unspalsh Source API](https://unsplash.com/developers), what’s yours? May the 4th be with you, Alex - [PreviousMigrating from WordPress to 11ty: enhancing website performance and control](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) - [NextHow to use structured data (schema markup) in HubSpot to improve SEO](/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo/) --- ## I walked away from WaveBox and JetBrains in the same month URL: https://alex.zappa.dev/blog/tool-migration-wavebox-jetbrains Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. Ten years is a long time to use one browser. I know because I just stopped. Two major tool switches in three weeks. Not because anything broke. Because everything got worse, month by month, and I finally stopped lying to myself about it. But the real catalyst wasn’t WaveBox or JetBrains. It was Claude. ## The Claude moment Back in June 2026, Anthropic dropped Fable 5. It was the superior model, a genuine leap forward, and it ran fast. For about three days. Two or three weeks before the launch, Claude Code started crawling. My hardware was the same. Nothing on my end changed. But their API was dragging: requests queuing, latency spiking, the whole thing felt like wading through mud. I checked my network. I checked my machine. Everything was fine. Then Fable 5 and Opus Fast landed, and suddenly the same API was flying again. Call it a coincidence if you want. I don’t. It felt like the old models got throttled right before the new ones shipped so everyone would step on the gas and feel the difference. Oh, so this is what we were missing. Sign here. That’s either brilliant product management or the kind of dark pattern that makes you rethink who you’re building on top of. Maybe both. On June 12, the US government applied export controls to Fable 5 and Mythos 5. Anthropic suspended access for everyone, globally, because they had no way to verify nationality in real time. One minute the model was there. The next minute it was gone. That was the wake-up call. I’m in the EU. I ship products under the FreshJuice brand. None of them are AI products. They’re regular software. But their development pipeline runs on Claude: PR reviews, changelogs, documentation, the boring stuff that happens between writing code and shipping it. If Claude goes down, the products keep working just fine. The velocity doesn’t. The PRs pile up. The changelog sits empty. The docs fall behind. That’s not “oh no, my Copilot is gone.” That’s your delivery rhythm breaking for a week because one vendor flipped a switch. I have ADHD. My brain doesn’t move in straight lines. It jumps between ten things at once and half of them don’t make sense even to me. AI doesn’t write my code. It polishes the chaos into something coherent. It groups the noise into something useful. Without it, I’d ship a lot more garbage. With it, the garbage gets filtered before it lands. Fable 5 came back on July 1 after the export controls lifted. But the lesson didn’t go away: this whole ecosystem is a single point of failure. If the one company you depend on sneezes, you’re out of commission for weeks. That thought stayed with me. It made me look at every other tool I was locked into and ask the same question: what happens when this one goes sideways? Spoiler: this post covers the browser and the IDE. The AI agent itself, the thing I use to write, review, and ship, is a separate story. That one’s coming. The answer, for WaveBox and JetBrains, was already obvious. I just hadn’t acted on it yet. ## WaveBox: day one to done Thomas Beverley built WaveBox on the ashes of WMail. I was there for the funeral. WMail was his email client, and when he decided it was time, he sunset it and launched WaveBox instead. The pitch was exactly what I wanted: a fast, responsive desktop app that ran web services as PWAs with real integration between them. Minimal, not minimal-looking. Functional, not feature-bloated. It felt like a genuine breath of fresh air. The Electron years were solid. Then came the Chromium migration, and honestly, that was the golden era. Suddenly WaveBox was a real browser. Chrome extensions worked. Everything was fast. I was a paying subscriber from day one and never questioned it. Then the AI update landed, chasing tomorrow. I wanted to love it. Native AI inside the browser I already live in? Sign me up. What shipped was a janky sidebar that got in the way more than it helped. And that pattern became the rule: every release added something I didn’t ask for. The app got heavier. Glitchier. Optimizations that somehow made everything slower. Features nobody requested while the core experience rotted around the edges. I didn’t stay for the grandfathered subscription. I stayed because I didn’t want to leave my comfort zone. The Veteran plan, or Grandpa plan, whatever they called it, was a classy touch, and I genuinely tip my hat to Thomas for that kind of respect. But the plan wasn’t the anchor. The anchor was ten years of muscle memory. The problem is, the comfort zone stopped being comfortable. Every update added more weight, more glitches. Optimizations that somehow made everything slower. The zone wasn’t cozy anymore; it was a swamp. ## Zen: the browser that doesn’t want to be everything I switched to [Zen](https://zen-browser.app), a Firefox-based open-source browser. It’s fast. It’s light. It doesn’t have an AI sidebar pitching me summaries I don’t need. The first week was rough. Muscle memory kept pulling me back to WaveBox. I’d open Zen, do one thing, then catch myself back in WaveBox for the next five. By week three I stopped opening it entirely. A decade of habit, gone in fourteen days. I haven’t uninstalled WaveBox yet. It’s sitting there, grandfathered subscription still active, like a gym membership you keep paying for because canceling means admitting you’re not going back. I’ll cancel it. Soon. Probably. ## JetBrains: paid through 2028, walked away mid-2026 I used JetBrains IDEs across every language I touched. WebStorm for frontend. PHPStorm when PHP paid the bills. RustRover when I picked up Rust. I was deep enough in the ecosystem that I paid three years in advance, through September 2028. That’s how confident I was. And it’s not that my machine is weak. It isn’t. It’s that [Zed](https://zed.dev) exists now, and the gap got impossible to ignore. Zed is an IDE written from scratch in Rust. Not a lightweight editor wearing an IDE hat. It does everything I actually need: LSP, terminal, Git, and the Copilot/Zed AI integration that’s fast and stays out of the way. It launches instantly. It doesn’t index my entire project for thirty seconds every time I open a folder. It doesn’t re-index every time I switch branches. WebStorm did that constantly, and even with 64 GB of RAM, JetBrains always found a way to fill it up. My MBP’s fans would spin up to vacuum-cleaner levels and just stay there. Apple Silicon is supposed to run cool. JetBrains made it thermal-throttle like a gaming laptop from 2015. ### The IDE migration Getting used to it felt like learning to ride a bike. Awkward for a day or two. Then your hands know where everything is and you stop thinking about it. About two weeks in, I realized I hadn’t opened WebStorm all day. Haven’t looked back. One thing I did lose: Gerry Themes Pro. It’s a paid JetBrains plugin with gorgeous color schemes. I used a dark one called Deep Dark Space or something close to that. Losing it stung a little. So I baked my own theme for Zed: [freshjuice-dev/zed-themes](https://github.com/freshjuice-dev/zed-themes). My daily driver is CyberBanana. Yes, that’s the actual name. No, I’m not changing it. ## The part nobody talks about People treat switching browsers like surgery. “But my bookmarks! My passwords! My extensions!” I’ve never relied on any of that. I don’t trust browser sync and never have. The switch was clean because the hard part was the habit, not the data. Here’s what I actually care about: memory. JetBrains IDEs are memory hogs. Everyone knows this. WaveBox on Chromium isn’t far behind. Between the two of them, a fat slice of my RAM was spoken for before I even opened a terminal. Now I run Zen and Zed side by side and I have enough headroom to run [qwen3.8 27B](https://huggingface.co/Qwen/Qwen3-8B) locally. Not quantized to death. Not swapped to disk. Actually running. The tools I paid for were eating the resources I needed for the tools I actually wanted to use. ## Decade done WaveBox was a great ride. Thomas built something that genuinely changed how I worked on the web, and I was happy to pay for it for ten years. The Veteran plan was a classy touch. But great rides end. Zed and Zen won’t be the last tools I switch to. The world moves faster now. Something new drops every few months. The difference is I’m no longer paying for software that gets slower with every update while chasing features I didn’t ask for. The next switch won’t take me two weeks. May the 4th be with you, Alex - [PreviousClaude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) - [NextThe models were never the magic](/blog/models-were-never-the-magic/) --- ## Track UTM parameters for HubSpot forms like a BOSS URL: https://alex.zappa.dev/blog/track-utm-parameters-for-hubspot-forms-like-a-boss Learn how to create and track marketing campaigns by using tracking URLs with HubSpot. Pass UTM parameters to hidden form fields using JavaScript. Store and feed form fields in sessionStorage. Install the code in the site footer of your HubSpot website. I think a Tracking URL is a good feature for marketing perspectives. > Tracking URLs help you measure the effectiveness of your marketing campaigns by providing information to HubSpot when visitors access your site through the URL. > > Create a tracking URL when you want to track traffic from a campaign to a page with the HubSpot tracking code. Examples include email blasts, PPC campaigns, and banner ads. You can also create a tracking URL using your [meeting links](https://knowledge.hubspot.com/meetings-tool/create-and-edit-scheduling-pages). > > A tracking URL is a normal URL with parameters attached to it. When visitors come to your site from a tracking URL, HubSpot saves the information in those parameters. Learn how to [analyze traffic data from tracking URLs in the traffic analytics tool](https://knowledge.hubspot.com/reports/analyze-your-site-traffic-with-the-traffic-analytics-tool#analyze-your-traffic-using-the-utm-parameters-tab). > > _From [Create Tracking URL’s](https://knowledge.hubspot.com/settings/how-do-i-create-a-tracking-url)_ Meantime, it’s good idea to pass those UTM parameters to form hidden fields > HubSpot makes it relatively easy to pass UTM parameters from a URL into hidden fields in a HubSpot form. In this blog post, we will outline step-by-step how to do so. _From [How to pass UTM parameters into a HubSpot form](https://align.ly/blog/how-to-pass-utm-parameters-into-a-hubspot-form/)_ The downside of the method described in article above it’s work only with landing page, when user arrives to your page via URL with UTM parameters, and if he goes to the next page or check some pages before actually do some actions with form on landing page, the UTMs will be lost. So basicly we looking to pass UTMs to form fields with property names: `utm_source`, `utm_campaign`, `utm_content`, `utm_medium`, `utm_term`. ## Store UTM parameters in sessionStorage ### What is sessionStorage? > The read-only **sessionStorage** property accesses a session [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) object for the current [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin). sessionStorage is similar to [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage); the difference is that while data in localStorage doesn’t expire, data in sessionStorage is cleared when the page session ends. > > _From [Web docs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)_ ### Custom JavaScript module will help store and feed form fields First what we have to learn about JavaScript on systems like HubSpot, always encapsulate your JavaScript using Anonymous Functions, to avoid global issues. ``` (function () { // do something })(); ``` This simple encapsulation example will help you exclude your code from global variables, also will not have to override your or third-party modules on HubSpot. Moreover, it can make your code a bit tiny and clean if you pass some global variables to the function, like next example. ``` (function ($, w, d) { // do something })(jQuery, window, document); ``` In the example above, we pass `jQuery`, `window`, and `document` as variables to the function which allow us to use `$` as `jQuery` alias, and use a short name for `window` and `document`. To get the query string component of the URL we need a small helper function like this: ``` function GET(param, url) { url = url ? url : window.location.href; var vars = {}; url.replace(window.location.hash, '').replace(/[?&]+([^=&]+)=?([^&]*)?/gi, function (m, key, value) { vars[key] = value !== void 0 ? value : ''; }); return typeof vars[param] !== 'undefined' ? vars[param] : null; } ``` This small function help us to extract query parameters from current URL. And here is how we could use the new `GET` function to extract UTMs from URL ``` var utm_source = GET('utm_source'); var utm_campaign = GET('utm_campaign'); var utm_content = GET('utm_content'); var utm_medium = GET('utm_medium'); var utm_term = GET('utm_term'); ``` Next we have to add Event Listener to populate forms after they have been rendered on webpage, to achieve that we have to check [HubSpot Forms API](https://legacydocs.hubspot.com/global-form-events) and check event called `onFormReady`, here is example how to use it: ``` window.addEventListener('message', function(event) { if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormReady') { console.log("Form Ready!"); // do something } }); ``` After all, let’s combine all code we have right now to one module: ``` (function ($, w, d, storage, GET) { // Creat object and store UTMs var utm = { source: GET('utm_source'), campaign: GET('utm_campaign'), content: GET('utm_content'), medium: GET('utm_medium'), term: GET('utm_term') }; // Check if one or more UTMs present in URL if (utm.source || utm.campaign || utm.content || utm.medium || utm.term) { storage.setItem('utm_params', JSON.stringify(utm)); } // Check if UTM parameters stored in SessionStorage if (typeof storage.getItem('utm_params') === 'string') { // Parse UTMs from SessionStorage to JSON var storedUTM = JSON.parse(storage.getItem('utm_params')); // Add Event Listener for `onFormReady` event w.addEventListener('message', function(e) { if (e.data.type === 'hsFormCallback' && e.data.eventName === 'onFormReady') { // Find all HubSpot forms on the page var scope = $(d).find('form[data-form-id]'); // Check if UTM parameter exists and populate form hidden input fields if (storedUTM.source) { scope.find('[name="utm_source"]').val(storedUTM.source).change(); } if (storedUTM.campaign) { scope.find('[name="utm_campaign"]').val(storedUTM.campaign).change(); } if (storedUTM.content) { scope.find('[name="utm_content"]').val(storedUTM.content).change(); } if (storedUTM.medium) { scope.find('[name="utm_medium"]').val(storedUTM.medium).change(); } if (storedUTM.term) { scope.find('[name="utm_term"]').val(storedUTM.term).change(); } } }); } })(jQuery, window, document, sessionStorage, function (param, url) { url = url ? url : window.location.href; var vars = {}; url.replace(location.hash, '').replace(/[?&]+([^=&]+)=?([^&]*)?/gi, function (m, key, value) { vars[key] = value !== void 0 ? value : ''; }); return typeof vars[param] !== 'undefined' ? vars[param] : ''; }) ``` ### Installation to HubSpot website Open Settings > Website(sidebar) > Pages(sidebar) > Templates(tab) > Site footer HTML(section) and paste the final code toSite footer HTML editor between script tags ``` ``` ![HubSpot Site Footer HTML Editor](/_astro/hubsput-site-footer-html-editor.BDwfAQHL_FKXNa.webp) _Final result should look like screenshot_ The same method could be used for any websites(Webflow, WordPress, Wix, Static, etc
) who use HubSpot forms. This is it! Next time, I will cover the way [how to store UTM parameters in cookie files](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/). May the 4th be with you, Alex - [PreviousWP plugin Elementor Multidomain Support](/blog/wp-plugin-elementor-multidomain-support/) - [NextKeep Track UTM parameters for HubSpot forms like Cookie Monster](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/) --- ## Transitioning Google URL shortener to Bitly URL: https://alex.zappa.dev/blog/transitioning-google-url-shortener-to-bitly Campaign URL Builder plugin is moving to Bitly API endpoint; support for goo.gl URL shortener will be turned down in March 2019. Previously created links will redirect. On [Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) plugin, I decided finally move to Bitly API endpoint. Since March 2019, [Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) plugin will be turning down support for goo.gl URL shortener. Previously created links will continue to redirect to their intended destination. Please see this [Google blog post](https://developers.googleblog.com/2018/03/transitioning-google-url-shortener.html) for more details. May the 4th be with you, Alex - [PreviousWP plugin Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) - [NextCampaign URL Builder Introducing the Shortcodes](/blog/campaign-url-builder-introducing-the-shortcodes/) --- ## Upgrading to Eleventy v3 URL: https://alex.zappa.dev/blog/upgrading-to-eleventy-v3 I took some time this week to upgrade my site to the newest version of Eleventy. Here's what I learned. This post inspired by Max’s blog about [Upgrading to Eleventy v3](https://mxb.dev/blog/eleventy-v3-update/) ![Eleventy Possum](/_astro/11ty-possum.D8TWAm6X_ZF7FvF.webp) Right after [11tyConf](https://conf.11ty.dev) which was in May of this year, I want to try a new 11ty version and test new image transform plugin. Although v3.0.0 is still in alpha, and I’m sure it will be released soon enough that we can use the latest version for testing production, at least for personal websites like mine. ## Switching over to ESM The biggest change in v3 is that Eleventy switches to use [ECMAScript Module Syntax](https://nodejs.org/api/esm.html) (ESM). That aligns it with contemporary standards for JS packages. In “[Lessons learned moving Eleventy from CommonJS to ESM](https://www.zachleat.com/web/eleventy-v3-esm/)”, Zach explains the motivation for the switch. Meantime, on projects where I work and worked usually was a mix of ESM and commonJS, and I never really thought too much about switch until now. ### Step 1: A package type changes I thought it would be all I had to do for the first time, update the `package.json` by adding new property `type` and set value to `module`. ``` // package.json { "name": "alex.zappa.dev", "version": "7.0.0", "type": "module", // ... } ``` Well
 it was just a start before realizing there are so many other files and scripts I have to update. ### Step 2: update all JS file Since all JS files are modules now, they need updating as well. Basically we cant use CommonJS syntax like `module.exports = thing` or `require('thing')` anymore, and we need to replace it with ESM module syntax instead. In an Eleventy v2 project, you’ll typically have your `eleventy.config.js`, files for filters/shortcodes and global data files that may look something like this: ``` const plugin = require('plugin-package') // ... module.exports = { something: plugin({ do: 'something' }) } ``` Using ESM syntax, rewrite these files to look like this: ``` import plugin from 'plugin-package' // ... export default { something: plugin({ do: 'something' }) } ``` It was repetitive work, but once done, it’s worth every second. ### Step 3: postCSS issues Since I use postCSS with TailwindCSS in my website, I also have to update postCSS and TailwindCSS config files, this is where I found one small issue with `tailwindcss/nesting` plugin. Its not ready to be used yet, and cant be imported like most of other plugins. To solve issue, I found one thread on [github issues](https://github.com/tailwindlabs/tailwindcss/issues/5089), the solution was simple, instead of importing tailwindcss as: ``` import tailwindNesting from "tailwindcss/nesting"; ``` you have to specify which file you looking to import ``` import tailwindNesting from "tailwindcss/nesting/index.js"; ``` ### Step 4: Image Transform [Eleventy image plugin](https://www.11ty.dev/docs/plugins/image/#eleventy-transform) got a new feature to optimize any `` tag on fly using eleventyTransform. Implementation is simple, and make you code clean, since you no need to use shortcodes for images anymore ``` import { eleventyImageTransformPlugin } from "@11ty/eleventy-img"; export default function (eleventyConfig) { eleventyConfig.addPlugin(eleventyImageTransformPlugin, { // which file extensions to process extensions: "html", // Add any other Image utility options here: // optional, output image formats formats: ["avif", "webp", "auto"], // optional, output dir and url path for images outputDir: "./_site/img/", urlPath: "/img/", // optional, output image widths widths: ["auto"], // optional, attributes assigned on override these values. defaultAttributes: { loading: "lazy", decoding: "async", } }); } ``` This code broke my website, because I forgot to update shortcode implementation first. So to solve this problem I have to add an attribute `eleventy:ignore` to the image shortcode: ``` import eleventyImage from "@11ty/eleventy-img"; // Only one module.exports per configuration file, please! export default function (eleventyConfig) { eleventyConfig.addShortcode("image", async function (src, alt, sizes) { // ... let imageAttributes = { alt, sizes, "eleventy:ignore": "", loading: "lazy", decoding: "async", }; // ... }); }; ``` And as result it works perfectly fine, but I want to replace most of shortcodes to regular `` tags, and I did it. ## Conclusion If your project is similar than mine then you can prepare your project to migrate to ESM syntax now, before Eleventy v3 will be released. Or if you like me, who like to use latest technology in web development, do that a soon as you can. May the 4th be with you, Alex - [PreviousCustom user avatars for WordPress](/blog/custom-user-avatars-for-wordpress/) - [NextwebChronicle: your personal Wayback Machine](/blog/webchronicle/) --- ## webChronicle: your personal Wayback Machine URL: https://alex.zappa.dev/blog/webchronicle A web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. In October 10. 2024 I was looking an archived copy of the website on the [Wayback Machine](https://web.archive.org/) and found that the web archive is down. ![Wayback Machine is down!](/_astro/wayback-machine-down.BESnx7cx_Z1ubdzW.webp) Shortly I started looking at internet media, what’s happened to one of the useful resources in the internet, and found Wayback Machine is down for a reason of DDoS attack. Here is a [Forbes article about this incident](https://www.forbes.com/sites/daveywinder/2024/10/10/internet-hacked-wayback-machine-down-31-million-passwords-stolen/). This case brings me to the idea of creating a personal web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. ## Meet the webChronicle The [webChronicle](https://webchronicle.dev/) is a simple tool that allows you to capture and explore snapshots of webpages over time. It’s like the Wayback Machine, but as your own personal Time Machine. ![Back To The Future - We have to go back, Marty! To save the World? No, to save the Web!](/_astro/bttf-meme.oVGyCYE1_ZXNUUH.webp) ### How it works The webChronicle builds on the [Eleventy](https://www.11ty.dev/) static site generator and the [website-scraper](https://www.npmjs.com/package/website-scraper) package to capture and store snapshots of webpages. As a result you will have a static website that you can host on [Netlify](https://www.netlify.com/) or [Vercel](https://vercel.com/) to explore snapshots of webpages over time. [![Netlify Deploy](https://www.netlify.com/img/deploy/button.svg)Netlify Deploy](https://app.netlify.com/start/deploy?repository=https://github.com/reatlat/webchronicle)[![Vercel Deploy](https://vercel.com/button)Vercel Deploy](https://vercel.com/import/project?template=https://github.com/reatlat/webchronicle) You also may deploy this project to other platforms like Heroku, AWS, Cloudflare Pages or Google Cloud. ### How to use 1. Clone the [webChronicle repository](https://github.com/reatlat/webchronicle/). 2. Install dependencies with `npm install`. 3. Update `webchronicle.config.js` with your configuration: ``` module.exports = { ... urls: [ 'https://example.com', 'https://example.org', ], urlFilter: (url) => { return url.includes('example.com') || url.includes('example.org'); }, ... }; ``` Full configuration options are available in the [Options](https://github.com/website-scraper/node-website-scraper?tab=readme-ov-file#options) section. 4. Run the scraper: ``` npm run scraper ``` 5. Commit the changes to your repository: ``` git add ./scrapped-websites git commit -m "Scrapped websites" git push ``` 6. Deploy the project to your preferred platform or run it locally `http://localhost:8080`: ``` npm run start ``` 7. Explore the snapshots of the webpages over time. 8. Enjoy! 🎉 ### Live demo You can explore the live demo of the webChronicle at [webChronicle](https://webchronicle.dev/). ## Credits Special thanks to [James Dancer](https://www.linkedin.com/in/james-dancer/) for the inspiration behind the name. Your idea was spot on! ## Contributing and source code You can find the source code of the webChronicle on [GitHub](https://github.com/reatlat/webchronicle/). If you notice an issue, feel free to [open an issue](https://github.com/reatlat/webchronicle/issues). Happy Holidays, and Thanks for reading! 🙏 May the 4th be with you, Alex - [PreviousUpgrading to Eleventy v3](/blog/upgrading-to-eleventy-v3/) - [NextHow to manage multiple SSH keys for multiple GitHub accounts on one computer](/blog/how-to-manage-multiple-ssh-keys-for-multiple-github-accounts-on-one-computer/) --- ## WP plugin Campaign URL Builder URL: https://alex.zappa.dev/blog/wp-plugin-campaign-url-builder Learn how to track Custom Campaigns in Google Analytics with this plugin. Easily create short links and share them on social networks. Get and install the plugin for WordPress from the repository or GitHub. Use the shortcode to generate shortlinks on any page. May the 4th be with you! This plugin allows you to easily add campaign parameters to URLs so you can track Custom Campaigns in Google Analytics. Also, you can easily create short links and easily share it on social networks. ## Get and Install plugin for WordPress _From WordPress repository_ Or alternative repository on GitHub, [the latest release](https://github.com/reatlat/wp-campaign-url-builder/releases). ## Shotcode ### What is a shortcode? A shortcode is a WordPress-specific code that lets you do nifty things with very little effort. Shortcodes can embed files or create objects that would normally require lots of complicated, ugly code in just one line. Shortcode = shortcut. Campaign URL Builder shortcode allows you to place presented shortlink generator on any page content. ### Shortcode generator Coming a soon as possible
 ? May the 4th be with you, Alex - [PreviousGitKraken – cross-platform editor for git repos](/blog/gitkraken-cross-platform-editor-for-git-repos/) - [NextTransitioning Google URL shortener to Bitly](/blog/transitioning-google-url-shortener-to-bitly/) --- ## WP plugin Elementor Multidomain Support URL: https://alex.zappa.dev/blog/wp-plugin-elementor-multidomain-support A plugin for Elementor Page Builder that adds multi-domain support for Multilanguage websites. Available on WordPress repository and GitHub. This plugin will help those who have a Multilanguage website with a multi-domain structure, the plugin will add to Elementor Page Builder the support for multi-domains structure. The source code of plugin can be found here [https://github.com/reatlat/wp-elementor-multidomain-support](https://github.com/reatlat/wp-elementor-multidomain-support) Plugin available in [WordPress repository](https://wordpress.org/plugins/multidomain-support-for-elementor/), and also you can download it from [GitHub](https://github.com/reatlat/wp-multidomain-support-for-elementor/releases/) May the 4th be with you, Alex - [PreviousHow to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) - [NextTrack UTM parameters for HubSpot forms like a BOSS](/blog/track-utm-parameters-for-hubspot-forms-like-a-boss/) --- ## 2025 year in review: a year of building, learning, and gratitude URL: https://alex.zappa.dev/blog/year-in-review-2025 Reflecting on a year filled with code, open-source contributions, and the amazing people who made it all worthwhile. If you’ve noticed my blog has been quiet this year, there’s a good reason for that. I’ve been heads-down, completely immersed in development, research, and building things I’m genuinely proud of. ## The numbers tell a story Looking at my GitHub profile, this year brought: ![GitHub Contributions 2025](/_astro/github-stats-2025.wJDRVMwS_1prdnv.webp) - **4,738+ contributions** across various projects - **81 repositories** across my [personal](https://github.com/reatlat) and [FreshJuice](https://github.com/freshjuice-dev) accounts - **7+ Eleventy plugins** published and maintained - Multiple HubSpot themes and modules - And countless hours of learning, debugging, and shipping But numbers only tell part of the story. ## FreshJuice: from side project to passion Most of my energy this year went into [FreshJuice](https://freshjuice.dev/), a creative development studio focused on HubSpot CMS and modern web development. What started as a way to share HubSpot development knowledge evolved into something bigger: - **[FreshJuice HubSpot Theme](https://github.com/freshjuice-dev/freshjuice-dev-hubspot-theme)** - A developer starter theme with Tailwind CSS and Alpine.js (27 stars, 12 forks) - **[Professional HubSpot Modules](https://demo.freshjuice.dev/)** - 4 premium modules for HubSpot CMS - **[HubSpot DevTools Extension](https://freshjuice.dev/hubspot-devtools/)** - A browser extension I use every single day - **[FreshJuice 11ty Starter](https://github.com/freshjuice-dev/freshjuice-11ty-starter)** - A modern Eleventy starter theme with CloudCannon CMS integration - **[Free Online Tools](https://freshjuice.dev/tools/)** - A collection of 15+ free utilities for developers and marketers ### Free tools for everyone Speaking of tools, this year I built and launched a whole suite of [free online utilities](https://freshjuice.dev/tools/) on FreshJuice: - **SEO Tools**: Broken Link Checker, Keyword Density Checker, Metadata Checker, Robots.txt Analyzer, SEO Analyzer, Readability Score Checker - **AI/LLM Tools**: [llms.txt Generator](https://freshjuice.dev/tools/llmstxt-generator/) for creating AI-friendly site documentation - **Development Tools**: CSS Clamp() Font Size Generator, UUID Generator - **Marketing Tools**: [Campaign URL Builder](https://campaign-url-builder.com/) for UTM tracking All completely free, no sign-up required. Just open and use. ### CloudCannon partnership A highlight of the year was partnering with [CloudCannon](https://cloudcannon.com/). The FreshJuice 11ty Starter is now available in their community themes, featuring full visual editing and page builder capabilities. If you’re looking for a modern Eleventy starter with a powerful CMS, [check it out](https://snappy-lemon.cloudvent.net/)! I’ve also been maintaining and improving several Eleventy plugins: - [eleventy-plugin-phosphoricons](https://github.com/reatlat/eleventy-plugin-phosphoricons) - [eleventy-plugin-speculation-rules](https://github.com/reatlat/eleventy-plugin-speculation-rules) - [eleventy-plugin-hubspot](https://github.com/reatlat/eleventy-plugin-hubspot) - [eleventy-plugin-vidyard](https://github.com/reatlat/eleventy-plugin-vidyard) - And a few more
 ## Kandji to Iru: my main gig My primary work continues to be with the amazing marketing team at Kandji, which transitioned to [Iru](https://www.iru.com/) this year. As part of the team, I build and maintain their HubSpot infrastructure, internal tools, and marketing websites. I even spent six months working from Kandji’s London HQ, where I had the chance to meet and collaborate with some truly incredible people in person. To the entire team: thank you for your trust, collaboration, and the opportunity to build something meaningful together. ### A special thank you to Sylvia I want to give a special shoutout to [Sylvia LePoidevin](https://www.linkedin.com/in/sylvialepoidevin/), who I had the pleasure of working with for almost 8 years, even before Kandji. This year, Sylvia decided to start something new: her own journey in the marketing world. She launched [Zero to One Marketer](https://www.zerotoonemarketer.com/), a blog sharing her insights on marketing, growth, and building from scratch. If you’re in marketing or just curious about the field, I highly recommend checking it out. Her experience leading marketing teams and building brands from the ground up makes her perspective incredibly valuable. Thank you, Sylvia, for almost 8 years of collaboration and the countless projects we built together. Wishing you all the best in this exciting new chapter! ## Discovering LinkedIn (yes, really) Here’s something unexpected: I started actively posting on LinkedIn in July this year, and I’m genuinely surprised by what I found. What inspired me to try? Actually, it was Sylvia. After reading [her viral post](https://www.linkedin.com/posts/sylvialepoidevin_what-i-learned-from-a-post-that-got-458319-activity-7340505273695920129-sfON) about lessons from going 0 to 1 as the first marketing hire (458,000+ impressions!), I thought: “Maybe I should give this a real shot too.” I expected the usual corporate noise, but instead discovered a warm, supportive community of developers, marketers, and fellow HubSpot enthusiasts who actually engage, share insights, and help each other grow. The numbers speak for themselves: ![LinkedIn Stats 2025](/_astro/linkedin-stats-2025.AiPcIFPS_Z1wLGgs.webp) - **18,623 impressions** in just 6 months (starting from zero!) - **Nearly 4,000 members reached** with my content - **1,000+ followers** who decided to join the journey These might not be viral numbers, but for me they’re huge. I’ve always treated this blog as a personal journal rather than a platform for engagement: no comment sections, no analytics obsession, just me and my thoughts. LinkedIn changed that. For the first time, I have a space to actually connect with people who share my interests, exchange ideas, and learn from each other. Every like, comment, and connection has meant more than you know. ## This website got a refresh too And yes, while writing this very post, I updated the design of this website! The navbar now features a liquid glass effect with backdrop blur, a smart sticky header that hides on scroll down and shows on scroll up, and a slick animated mobile menu. I also added a [search functionality](/search/) powered by [Pagefind](https://pagefind.app/), an amazing static search tool created by the CloudCannon team. Sometimes you just need to practice what you preach and ship something for yourself. I’ve also been diving deep into accessibility. I set up automated tests to ensure this site passes [WCAG 2.1 AA](https://www.w3.org/WAI/WCAG21/quickref/) validation, with plans to reach AAA where possible. The WCAG and ADA guidelines have evolved significantly, covering nuances I used to overlook. In the coming year, I’m committed to learning them all and building better web experiences. When design constraints make full compliance tricky, I’ll add controls like high-contrast modes to ensure everyone can use my sites comfortably. ## Gratitude This year reminded me why I love what I do. It’s not just about the code, it’s about the people. Thank you to: - **The Iru team** (formerly Kandji) for the incredible partnership - **[The FreshJuice community](https://www.linkedin.com/company/freshjuice)** for using, starring, and contributing to our open-source projects - **[My LinkedIn connections](https://www.linkedin.com/in/reatlat/)** for the unexpected warmth and engagement - **[The Eleventy community](https://www.11ty.dev/docs/community/)** for building such an amazing ecosystem - **[The HubSpot developer community](https://developers.hubspot.com/community)** for the constant inspiration - **Everyone who read my posts, opened an issue, or sent a kind message** ## Looking Ahead 2026 is around the corner, and I have plans. More open-source tools, more sharing, and hopefully more blog posts here (I promise to write more!). > If you get just 1% better each day, you’ll end up 37 times better by the end of the year. > > — James Clear, [Atomic Habits](https://jamesclear.com/atomic-habits) Small steps, big impact. Let’s keep pushing for progress and spreading good into the world. But for now, I’m taking a moment to appreciate how far we’ve come. Thank you for being part of this journey. Happy Holidays and Happy New Year! 🎉 May the 4th be with you, Alex - [PreviousGuest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) - [NextHow I replaced my ISP router and extracted PPPoE credentials using a bridge sniffer](/blog/isp-router-replacement/) --- ## Privacy Policy URL: https://alex.zappa.dev/privacy Privacy Policy for Alex Zappa's website. # Privacy Policy ## Cookies This website does not use cookies. However, my website may use third party services that may use cookies. These services are: - [CodePen](https://blog.codepen.io/documentation/privacy-policy/#cookies) - CodePen embeds are loaded only after you explicitly click "Load CodePen". You can choose to always load CodePen, which saves your preference in localStorage. ### How to disable 3rd party cookies You can disable 3rd party cookies in your browser settings. For more information, please visit [All About Cookies](https://www.allaboutcookies.org/manage-cookies/). ## Local Storage This website stores the following preferences in your browser's localStorage: - **theme** - Your preferred color scheme (only saved when you click the theme toggle) - **codepen-consent** - Your choice to always load CodePen embeds ### Your current stored data Clear all preferences ## Analytics This website does not use any analytics or tracking services. --- ## Tags URL: https://alex.zappa.dev/tags Browse posts by tag. # Tags - [#wordpress6](/tags/wordpress/) - [#ai4](/tags/ai/) - [#css4](/tags/css/) - [#eleventy4](/tags/eleventy/) - [#git4](/tags/git/) - [#hubspot4](/tags/hubspot/) - [#oss4](/tags/oss/) - [#plugin4](/tags/plugin/) - [#campaign-url-builder3](/tags/campaign-url-builder/) - [#featured3](/tags/featured/) - [#migration3](/tags/migration/) - [#posts3](/tags/posts/) - [#anthropic2](/tags/anthropic/) - [#astro2](/tags/astro/) - [#claude2](/tags/claude/) - [#code2](/tags/code/) - [#forms2](/tags/forms/) - [#github2](/tags/github/) - [#linux2](/tags/linux/) - [#macos2](/tags/macos/) - [#seo2](/tags/seo/) - [#tracking2](/tags/tracking/) - [#utm2](/tags/utm/) - [#11ty1](/tags/11ty/) - [#access token1](/tags/access-token/) - [#aieo1](/tags/aieo/) - [#api1](/tags/api/) - [#apple1](/tags/apple/) - [#appstore1](/tags/appstore/) - [#avatar1](/tags/avatar/) - [#bash1](/tags/bash/) - [#bitly1](/tags/bitly/) - [#browser1](/tags/browser/) - [#cloudflare1](/tags/cloudflare/) - [#content1](/tags/content/) - [#copyright1](/tags/copyright/) - [#cross-platform1](/tags/cross-platform/) - [#css31](/tags/css3/) - [#curl1](/tags/curl/) - [#dark-mode1](/tags/dark-mode/) - [#debian1](/tags/debian/) - [#debugging1](/tags/debugging/) - [#deepseek1](/tags/deepseek/) - [#devops1](/tags/devops/) - [#ds\_store1](/tags/ds_store/) - [#elementor1](/tags/elementor/) - [#europe1](/tags/europe/) - [#free1](/tags/free/) - [#freshjuice1](/tags/freshjuice/) - [#gdpr1](/tags/gdpr/) - [#geoip1](/tags/geoip/) - [#gitkraken1](/tags/gitkraken/) - [#gravatar1](/tags/gravatar/) - [#guest-post1](/tags/guest-post/) - [#hacks1](/tags/hacks/) - [#hermes1](/tags/hermes/) - [#homelab1](/tags/homelab/) - [#ide1](/tags/ide/) - [#image1](/tags/image/) - [#javascript1](/tags/javascript/) - [#jetbrains1](/tags/jetbrains/) - [#light-mode1](/tags/light-mode/) - [#llm1](/tags/llm/) - [#lorem1](/tags/lorem/) - [#mailchimp1](/tags/mailchimp/) - [#mockup1](/tags/mockup/) - [#multidomain1](/tags/multidomain/) - [#networking1](/tags/networking/) - [#nginx1](/tags/nginx/) - [#oauth1](/tags/oauth/) - [#ollama1](/tags/ollama/) - [#open-source1](/tags/open-source/) - [#orange-marketing1](/tags/orange-marketing/) - [#performance1](/tags/performance/) - [#personal1](/tags/personal/) - [#photo1](/tags/photo/) - [#php1](/tags/php/) - [#picsum1](/tags/picsum/) - [#pixabay1](/tags/pixabay/) - [#placehold1](/tags/placehold/) - [#placeholder1](/tags/placeholder/) - [#privacy1](/tags/privacy/) - [#responsive-images1](/tags/responsive-images/) - [#schema-markup1](/tags/schema-markup/) - [#shortcodes1](/tags/shortcodes/) - [#simplicity1](/tags/simplicity/) - [#ssh1](/tags/ssh/) - [#stock1](/tags/stock/) - [#structured-data1](/tags/structured-data/) - [#tailwind1](/tags/tailwind/) - [#tools1](/tags/tools/) - [#ubuntu1](/tags/ubuntu/) - [#unsplash1](/tags/unsplash/) - [#user-management1](/tags/user-management/) - [#vpn1](/tags/vpn/) - [#wavebox1](/tags/wavebox/) - [#web1](/tags/web/) - [#web-archiving1](/tags/web-archiving/) - [#webchronicle1](/tags/webchronicle/) - [#year-in-review1](/tags/year-in-review/) - [#zed1](/tags/zed/) - [#zen1](/tags/zen/) --- ## Tagged "11ty" URL: https://alex.zappa.dev/tags/11ty Posts tagged with #11ty. # Tagged "11ty" ## 2023 - 08/13 [Migrating from WordPress to 11ty: enhancing website performance and control](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) Discover the benefits of using 11ty for static website generation, including improved performance, simplicity, and control over your website. Take your online presence to new heights with 11ty. --- ## Tagged "access token" URL: https://alex.zappa.dev/tags/access-token Posts tagged with #access token. # Tagged "access token" ## 2019 - 01/19 [How do I find my Bitly OAuth access token?](/blog/how-do-i-find-my-bitly-oauth-access-token/) Learn how to sign up for a Bitly account and generate an access token for the Bitly API. Follow these steps to get started. --- ## Tagged "ai" URL: https://alex.zappa.dev/tags/ai Posts tagged with #ai. # Tagged "ai" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. ## 2025 - 10/08 [Guest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) The future of search isn't keywords, it's context. In my guest post for Orange Marketing, I explored how AI is reshaping SEO and how marketers and web developers can adapt. --- ## Tagged "aieo" URL: https://alex.zappa.dev/tags/aieo Posts tagged with #aieo. # Tagged "aieo" ## 2025 - 10/08 [Guest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) The future of search isn't keywords, it's context. In my guest post for Orange Marketing, I explored how AI is reshaping SEO and how marketers and web developers can adapt. --- ## Tagged "anthropic" URL: https://alex.zappa.dev/tags/anthropic Posts tagged with #anthropic. # Tagged "anthropic" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. --- ## Tagged "api" URL: https://alex.zappa.dev/tags/api Posts tagged with #api. # Tagged "api" ## 2016 - 11/08 [Adding subscribers to a list using Mailchimp’s API v3](/blog/adding-subscribers-to-a-list-using-mailchimps-api-v3/) Learn how to add and update list members in MailChimp's API v3 using a PUT request. Includes code example and tips. May the 4th be with you! --- ## Tagged "apple" URL: https://alex.zappa.dev/tags/apple Posts tagged with #apple. # Tagged "apple" ## 2022 - 01/21 [How to change App Store preferred Language 2022](/blog/how-to-change-app-store-preferred-language-2022/) Resolve your AppStore language issue on macOS! Despite numerous solutions, I discovered a simple fix for the problem. Change your VPN to UK location, log out of AppStore, force close it, and open again. After logging in, AppStore will switch to English. --- ## Tagged "appstore" URL: https://alex.zappa.dev/tags/appstore Posts tagged with #appstore. # Tagged "appstore" ## 2022 - 01/21 [How to change App Store preferred Language 2022](/blog/how-to-change-app-store-preferred-language-2022/) Resolve your AppStore language issue on macOS! Despite numerous solutions, I discovered a simple fix for the problem. Change your VPN to UK location, log out of AppStore, force close it, and open again. After logging in, AppStore will switch to English. --- ## Tagged "astro" URL: https://alex.zappa.dev/tags/astro Posts tagged with #astro. # Tagged "astro" ## 2026 - 06/23 [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. - 06/22 [Why I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. --- ## Tagged "avatar" URL: https://alex.zappa.dev/tags/avatar Posts tagged with #avatar. # Tagged "avatar" ## 2024 - 04/09 [Custom user avatars for WordPress](/blog/custom-user-avatars-for-wordpress/) Adding custom avatar functionality to your WordPress site. Simple and quick solutions to make your website even more attractive to users! --- ## Tagged "bash" URL: https://alex.zappa.dev/tags/bash Posts tagged with #bash. # Tagged "bash" ## 2026 - 01/11 [Migrating from Cloudflare Pages to Workers (and cleaning up the mess)](/blog/cloudflare-pages-to-workers-migration/) My experience merging an 11ty static site and API into a single Cloudflare Worker, plus a bash script to delete old Pages deployments when the dashboard won't let you. --- ## Tagged "bitly" URL: https://alex.zappa.dev/tags/bitly Posts tagged with #bitly. # Tagged "bitly" ## 2019 - 01/19 [How do I find my Bitly OAuth access token?](/blog/how-do-i-find-my-bitly-oauth-access-token/) Learn how to sign up for a Bitly account and generate an access token for the Bitly API. Follow these steps to get started. --- ## Tagged "browser" URL: https://alex.zappa.dev/tags/browser Posts tagged with #browser. # Tagged "browser" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. --- ## Tagged "campaign-url-builder" URL: https://alex.zappa.dev/tags/campaign-url-builder Posts tagged with #campaign-url-builder. # Tagged "campaign-url-builder" ## 2019 - 01/19 [Campaign URL Builder Introducing the Shortcodes](/blog/campaign-url-builder-introducing-the-shortcodes/) Introducing the first release of shortcodes for Campaign URL Builder plugin. Shortcodes can be customized with various attributes and can generate forms on the front-end. Bitly API key is required. Shortcodes can be styled and have optional parameters. Examples provided for simple, advanced, and PHP usage. - 01/14 [Transitioning Google URL shortener to Bitly](/blog/transitioning-google-url-shortener-to-bitly/) Campaign URL Builder plugin is moving to Bitly API endpoint; support for goo.gl URL shortener will be turned down in March 2019. Previously created links will redirect. - 01/01 [WP plugin Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) Learn how to track Custom Campaigns in Google Analytics with this plugin. Easily create short links and share them on social networks. Get and install the plugin for WordPress from the repository or GitHub. Use the shortcode to generate shortlinks on any page. May the 4th be with you! --- ## Tagged "claude" URL: https://alex.zappa.dev/tags/claude Posts tagged with #claude. # Tagged "claude" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. --- ## Tagged "cloudflare" URL: https://alex.zappa.dev/tags/cloudflare Posts tagged with #cloudflare. # Tagged "cloudflare" ## 2026 - 01/11 [Migrating from Cloudflare Pages to Workers (and cleaning up the mess)](/blog/cloudflare-pages-to-workers-migration/) My experience merging an 11ty static site and API into a single Cloudflare Worker, plus a bash script to delete old Pages deployments when the dashboard won't let you. --- ## Tagged "code" URL: https://alex.zappa.dev/tags/code Posts tagged with #code. # Tagged "code" ## 2024 - 06/23 [Upgrading to Eleventy v3](/blog/upgrading-to-eleventy-v3/) I took some time this week to upgrade my site to the newest version of Eleventy. Here's what I learned. ## 2021 - 11/29 [My favorite CSS hacks](/blog/my-favorite-css-hacks/) Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. --- ## Tagged "content" URL: https://alex.zappa.dev/tags/content Posts tagged with #content. # Tagged "content" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "copyright" URL: https://alex.zappa.dev/tags/copyright Posts tagged with #copyright. # Tagged "copyright" ## 2026 - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. --- ## Tagged "cross-platform" URL: https://alex.zappa.dev/tags/cross-platform Posts tagged with #cross-platform. # Tagged "cross-platform" ## 2018 - 11/09 [GitKraken – cross-platform editor for git repos](/blog/gitkraken-cross-platform-editor-for-git-repos/) GitKraken is a cross-platform git interface written in electron and open libraries, offering merge conflict tools and a task-tracking system. Get a $20Off discount for a one-year subscription. --- ## Tagged "css" URL: https://alex.zappa.dev/tags/css Posts tagged with #css. # Tagged "css" ## 2026 - 06/23 [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. ## 2021 - 11/29 [My favorite CSS hacks](/blog/my-favorite-css-hacks/) Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. - 11/21 [Detect dark or light system mode](/blog/detect-dark-or-light-system-mode/) Learn how to use prefers-color-scheme CSS media feature to detect user color theme preferences. See examples using media query and JavaScript. Also, track color mode with Google Tag Manager and Google Analytics. - 10/27 [Mobile friendly Responsive images with CSS](/blog/mobile-friendly-responsive-images-with-css/) Learn how to make responsive images in HTML and CSS. Discover how to prevent images from going outside the container. Find solutions for WordPress and HubSpot. --- ## Tagged "css3" URL: https://alex.zappa.dev/tags/css3 Posts tagged with #css3. # Tagged "css3" ## 2021 - 11/29 [My favorite CSS hacks](/blog/my-favorite-css-hacks/) Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. --- ## Tagged "curl" URL: https://alex.zappa.dev/tags/curl Posts tagged with #curl. # Tagged "curl" ## 2016 - 11/08 [Adding subscribers to a list using Mailchimp’s API v3](/blog/adding-subscribers-to-a-list-using-mailchimps-api-v3/) Learn how to add and update list members in MailChimp's API v3 using a PUT request. Includes code example and tips. May the 4th be with you! --- ## Tagged "dark-mode" URL: https://alex.zappa.dev/tags/dark-mode Posts tagged with #dark-mode. # Tagged "dark-mode" ## 2021 - 11/21 [Detect dark or light system mode](/blog/detect-dark-or-light-system-mode/) Learn how to use prefers-color-scheme CSS media feature to detect user color theme preferences. See examples using media query and JavaScript. Also, track color mode with Google Tag Manager and Google Analytics. --- ## Tagged "debian" URL: https://alex.zappa.dev/tags/debian Posts tagged with #debian. # Tagged "debian" ## 2019 - 04/14 [How to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) Block visitors by country using NGINX and the GeoIP module. Install the module and database, configure the nginx.conf file, and test the configuration before restarting nginx. Instructions with examples provided. --- ## Tagged "debugging" URL: https://alex.zappa.dev/tags/debugging Posts tagged with #debugging. # Tagged "debugging" ## 2026 - 06/23 [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. --- ## Tagged "deepseek" URL: https://alex.zappa.dev/tags/deepseek Posts tagged with #deepseek. # Tagged "deepseek" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. --- ## Tagged "devops" URL: https://alex.zappa.dev/tags/devops Posts tagged with #devops. # Tagged "devops" ## 2026 - 01/11 [Migrating from Cloudflare Pages to Workers (and cleaning up the mess)](/blog/cloudflare-pages-to-workers-migration/) My experience merging an 11ty static site and API into a single Cloudflare Worker, plus a bash script to delete old Pages deployments when the dashboard won't let you. --- ## Tagged "ds_store" URL: https://alex.zappa.dev/tags/ds_store Posts tagged with #ds_store. # Tagged "ds\_store" ## 2019 - 04/10 [How remove .DS\_Store files from a Git repository?](/blog/how-remove-dsstore-files-from-a-git-repository/) Learn how to remove .DS\_Store files from your Mac OS X repository. Use commands to find and remove the files. Update .gitignore for future prevention. Check gitignore.io for more configurations. --- ## Tagged "elementor" URL: https://alex.zappa.dev/tags/elementor Posts tagged with #elementor. # Tagged "elementor" ## 2019 - 04/21 [WP plugin Elementor Multidomain Support](/blog/wp-plugin-elementor-multidomain-support/) A plugin for Elementor Page Builder that adds multi-domain support for Multilanguage websites. Available on WordPress repository and GitHub. --- ## Tagged "eleventy" URL: https://alex.zappa.dev/tags/eleventy Posts tagged with #eleventy. # Tagged "eleventy" ## 2026 - 06/22 [Why I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. - 01/11 [Migrating from Cloudflare Pages to Workers (and cleaning up the mess)](/blog/cloudflare-pages-to-workers-migration/) My experience merging an 11ty static site and API into a single Cloudflare Worker, plus a bash script to delete old Pages deployments when the dashboard won't let you. ## 2024 - 12/01 [webChronicle: your personal Wayback Machine](/blog/webchronicle/) A web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. - 06/23 [Upgrading to Eleventy v3](/blog/upgrading-to-eleventy-v3/) I took some time this week to upgrade my site to the newest version of Eleventy. Here's what I learned. --- ## Tagged "europe" URL: https://alex.zappa.dev/tags/europe Posts tagged with #europe. # Tagged "europe" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. --- ## Tagged "featured" URL: https://alex.zappa.dev/tags/featured Posts tagged with #featured. # Tagged "featured" ## 2025 - 12/28 [2025 year in review: a year of building, learning, and gratitude](/blog/year-in-review-2025/) Reflecting on a year filled with code, open-source contributions, and the amazing people who made it all worthwhile. ## 2024 - 12/01 [webChronicle: your personal Wayback Machine](/blog/webchronicle/) A web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. ## 2021 - 11/29 [My favorite CSS hacks](/blog/my-favorite-css-hacks/) Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. --- ## Tagged "forms" URL: https://alex.zappa.dev/tags/forms Posts tagged with #forms. # Tagged "forms" ## 2021 - 10/22 [Keep Track UTM parameters for HubSpot forms like Cookie Monster](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/) Track UTMs and store them using cookies instead of sessionStorage. Use JavaScript Cookie library and add the code to the website footer. - 10/16 [Track UTM parameters for HubSpot forms like a BOSS](/blog/track-utm-parameters-for-hubspot-forms-like-a-boss/) Learn how to create and track marketing campaigns by using tracking URLs with HubSpot. Pass UTM parameters to hidden form fields using JavaScript. Store and feed form fields in sessionStorage. Install the code in the site footer of your HubSpot website. --- ## Tagged "free" URL: https://alex.zappa.dev/tags/free Posts tagged with #free. # Tagged "free" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "freshjuice" URL: https://alex.zappa.dev/tags/freshjuice Posts tagged with #freshjuice. # Tagged "freshjuice" ## 2026 - 06/22 [Why I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. --- ## Tagged "gdpr" URL: https://alex.zappa.dev/tags/gdpr Posts tagged with #gdpr. # Tagged "gdpr" ## 2026 - 03/10 [HubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) HubSpot's cookie consent banner secretly loads Google Fonts before you even consent, leaking your IP to Google. Here's how I fixed it with a JavaScript proxy hack. --- ## Tagged "geoip" URL: https://alex.zappa.dev/tags/geoip Posts tagged with #geoip. # Tagged "geoip" ## 2019 - 04/14 [How to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) Block visitors by country using NGINX and the GeoIP module. Install the module and database, configure the nginx.conf file, and test the configuration before restarting nginx. Instructions with examples provided. --- ## Tagged "git" URL: https://alex.zappa.dev/tags/git Posts tagged with #git. # Tagged "git" ## 2026 - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. ## 2024 - 12/06 [How to manage multiple SSH keys for multiple GitHub accounts on one computer](/blog/how-to-manage-multiple-ssh-keys-for-multiple-github-accounts-on-one-computer/) Learn how to set up and manage multiple SSH keys on a single computer to seamlessly work with multiple GitHub accounts. Step-by-step guide for developers. ## 2019 - 04/10 [How remove .DS\_Store files from a Git repository?](/blog/how-remove-dsstore-files-from-a-git-repository/) Learn how to remove .DS\_Store files from your Mac OS X repository. Use commands to find and remove the files. Update .gitignore for future prevention. Check gitignore.io for more configurations. ## 2018 - 11/09 [GitKraken – cross-platform editor for git repos](/blog/gitkraken-cross-platform-editor-for-git-repos/) GitKraken is a cross-platform git interface written in electron and open libraries, offering merge conflict tools and a task-tracking system. Get a $20Off discount for a one-year subscription. --- ## Tagged "github" URL: https://alex.zappa.dev/tags/github Posts tagged with #github. # Tagged "github" ## 2026 - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. ## 2024 - 12/06 [How to manage multiple SSH keys for multiple GitHub accounts on one computer](/blog/how-to-manage-multiple-ssh-keys-for-multiple-github-accounts-on-one-computer/) Learn how to set up and manage multiple SSH keys on a single computer to seamlessly work with multiple GitHub accounts. Step-by-step guide for developers. --- ## Tagged "gitkraken" URL: https://alex.zappa.dev/tags/gitkraken Posts tagged with #gitkraken. # Tagged "gitkraken" ## 2018 - 11/09 [GitKraken – cross-platform editor for git repos](/blog/gitkraken-cross-platform-editor-for-git-repos/) GitKraken is a cross-platform git interface written in electron and open libraries, offering merge conflict tools and a task-tracking system. Get a $20Off discount for a one-year subscription. --- ## Tagged "gravatar" URL: https://alex.zappa.dev/tags/gravatar Posts tagged with #gravatar. # Tagged "gravatar" ## 2024 - 04/09 [Custom user avatars for WordPress](/blog/custom-user-avatars-for-wordpress/) Adding custom avatar functionality to your WordPress site. Simple and quick solutions to make your website even more attractive to users! --- ## Tagged "guest-post" URL: https://alex.zappa.dev/tags/guest-post Posts tagged with #guest-post. # Tagged "guest-post" ## 2025 - 10/08 [Guest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) The future of search isn't keywords, it's context. In my guest post for Orange Marketing, I explored how AI is reshaping SEO and how marketers and web developers can adapt. --- ## Tagged "hacks" URL: https://alex.zappa.dev/tags/hacks Posts tagged with #hacks. # Tagged "hacks" ## 2021 - 11/29 [My favorite CSS hacks](/blog/my-favorite-css-hacks/) Discover CSS hacks and shortcuts, including using box-sizing, selection pseudo-class, caret color for input elements, displaying links when an element has no text value, fitting images to content, detecting dark system mode, hiding images on mobile, using CSS vendor prefixes for different browsers, and utilizing Autoprefixer. Learn these tips and improve your CSS coding skills. --- ## Tagged "hermes" URL: https://alex.zappa.dev/tags/hermes Posts tagged with #hermes. # Tagged "hermes" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. --- ## Tagged "homelab" URL: https://alex.zappa.dev/tags/homelab Posts tagged with #homelab. # Tagged "homelab" ## 2026 - 01/03 [How I replaced my ISP router and extracted PPPoE credentials using a bridge sniffer](/blog/isp-router-replacement/) A guide to replacing your Digi España router with your own hardware by capturing PPPoE credentials through packet sniffing. Includes bridge setup, Wireshark analysis, and GL.iNet configuration. --- ## Tagged "hubspot" URL: https://alex.zappa.dev/tags/hubspot Posts tagged with #hubspot. # Tagged "hubspot" ## 2026 - 03/10 [HubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) HubSpot's cookie consent banner secretly loads Google Fonts before you even consent, leaking your IP to Google. Here's how I fixed it with a JavaScript proxy hack. ## 2024 - 01/07 [How to use structured data (schema markup) in HubSpot to improve SEO](/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo/) How to use Structured Data, also referred to as Schema Markup, inside of HubSpot that is populated using HubL variables for use to improve SEO. ## 2021 - 10/22 [Keep Track UTM parameters for HubSpot forms like Cookie Monster](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/) Track UTMs and store them using cookies instead of sessionStorage. Use JavaScript Cookie library and add the code to the website footer. - 10/16 [Track UTM parameters for HubSpot forms like a BOSS](/blog/track-utm-parameters-for-hubspot-forms-like-a-boss/) Learn how to create and track marketing campaigns by using tracking URLs with HubSpot. Pass UTM parameters to hidden form fields using JavaScript. Store and feed form fields in sessionStorage. Install the code in the site footer of your HubSpot website. --- ## Tagged "ide" URL: https://alex.zappa.dev/tags/ide Posts tagged with #ide. # Tagged "ide" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. --- ## Tagged "image" URL: https://alex.zappa.dev/tags/image Posts tagged with #image. # Tagged "image" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "javascript" URL: https://alex.zappa.dev/tags/javascript Posts tagged with #javascript. # Tagged "javascript" ## 2026 - 03/10 [HubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) HubSpot's cookie consent banner secretly loads Google Fonts before you even consent, leaking your IP to Google. Here's how I fixed it with a JavaScript proxy hack. --- ## Tagged "jetbrains" URL: https://alex.zappa.dev/tags/jetbrains Posts tagged with #jetbrains. # Tagged "jetbrains" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. --- ## Tagged "light-mode" URL: https://alex.zappa.dev/tags/light-mode Posts tagged with #light-mode. # Tagged "light-mode" ## 2021 - 11/21 [Detect dark or light system mode](/blog/detect-dark-or-light-system-mode/) Learn how to use prefers-color-scheme CSS media feature to detect user color theme preferences. See examples using media query and JavaScript. Also, track color mode with Google Tag Manager and Google Analytics. --- ## Tagged "linux" URL: https://alex.zappa.dev/tags/linux Posts tagged with #linux. # Tagged "linux" ## 2026 - 01/03 [How I replaced my ISP router and extracted PPPoE credentials using a bridge sniffer](/blog/isp-router-replacement/) A guide to replacing your Digi España router with your own hardware by capturing PPPoE credentials through packet sniffing. Includes bridge setup, Wireshark analysis, and GL.iNet configuration. ## 2019 - 04/14 [How to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) Block visitors by country using NGINX and the GeoIP module. Install the module and database, configure the nginx.conf file, and test the configuration before restarting nginx. Instructions with examples provided. --- ## Tagged "llm" URL: https://alex.zappa.dev/tags/llm Posts tagged with #llm. # Tagged "llm" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. --- ## Tagged "lorem" URL: https://alex.zappa.dev/tags/lorem Posts tagged with #lorem. # Tagged "lorem" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "macos" URL: https://alex.zappa.dev/tags/macos Posts tagged with #macos. # Tagged "macos" ## 2022 - 01/21 [How to change App Store preferred Language 2022](/blog/how-to-change-app-store-preferred-language-2022/) Resolve your AppStore language issue on macOS! Despite numerous solutions, I discovered a simple fix for the problem. Change your VPN to UK location, log out of AppStore, force close it, and open again. After logging in, AppStore will switch to English. ## 2019 - 04/10 [How remove .DS\_Store files from a Git repository?](/blog/how-remove-dsstore-files-from-a-git-repository/) Learn how to remove .DS\_Store files from your Mac OS X repository. Use commands to find and remove the files. Update .gitignore for future prevention. Check gitignore.io for more configurations. --- ## Tagged "mailchimp" URL: https://alex.zappa.dev/tags/mailchimp Posts tagged with #mailchimp. # Tagged "mailchimp" ## 2016 - 11/08 [Adding subscribers to a list using Mailchimp’s API v3](/blog/adding-subscribers-to-a-list-using-mailchimps-api-v3/) Learn how to add and update list members in MailChimp's API v3 using a PUT request. Includes code example and tips. May the 4th be with you! --- ## Tagged "migration" URL: https://alex.zappa.dev/tags/migration Posts tagged with #migration. # Tagged "migration" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. - 06/23 [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. - 06/22 [Why I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. --- ## Tagged "mockup" URL: https://alex.zappa.dev/tags/mockup Posts tagged with #mockup. # Tagged "mockup" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "multidomain" URL: https://alex.zappa.dev/tags/multidomain Posts tagged with #multidomain. # Tagged "multidomain" ## 2019 - 04/21 [WP plugin Elementor Multidomain Support](/blog/wp-plugin-elementor-multidomain-support/) A plugin for Elementor Page Builder that adds multi-domain support for Multilanguage websites. Available on WordPress repository and GitHub. --- ## Tagged "networking" URL: https://alex.zappa.dev/tags/networking Posts tagged with #networking. # Tagged "networking" ## 2026 - 01/03 [How I replaced my ISP router and extracted PPPoE credentials using a bridge sniffer](/blog/isp-router-replacement/) A guide to replacing your Digi España router with your own hardware by capturing PPPoE credentials through packet sniffing. Includes bridge setup, Wireshark analysis, and GL.iNet configuration. --- ## Tagged "nginx" URL: https://alex.zappa.dev/tags/nginx Posts tagged with #nginx. # Tagged "nginx" ## 2019 - 04/14 [How to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) Block visitors by country using NGINX and the GeoIP module. Install the module and database, configure the nginx.conf file, and test the configuration before restarting nginx. Instructions with examples provided. --- ## Tagged "oauth" URL: https://alex.zappa.dev/tags/oauth Posts tagged with #oauth. # Tagged "oauth" ## 2019 - 01/19 [How do I find my Bitly OAuth access token?](/blog/how-do-i-find-my-bitly-oauth-access-token/) Learn how to sign up for a Bitly account and generate an access token for the Bitly API. Follow these steps to get started. --- ## Tagged "ollama" URL: https://alex.zappa.dev/tags/ollama Posts tagged with #ollama. # Tagged "ollama" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. --- ## Tagged "open-source" URL: https://alex.zappa.dev/tags/open-source Posts tagged with #open-source. # Tagged "open-source" ## 2026 - 08/22 [The models were never the magic](/blog/models-were-never-the-magic/) Mathias Biilmann says the price of intelligence finally matters. I went one step further: the model stopped mattering entirely. The difference was always in the harness. --- ## Tagged "orange-marketing" URL: https://alex.zappa.dev/tags/orange-marketing Posts tagged with #orange-marketing. # Tagged "orange-marketing" ## 2025 - 10/08 [Guest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) The future of search isn't keywords, it's context. In my guest post for Orange Marketing, I explored how AI is reshaping SEO and how marketers and web developers can adapt. --- ## Tagged "oss" URL: https://alex.zappa.dev/tags/oss Posts tagged with #oss. # Tagged "oss" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. - 08/12 [Claude Code signs every commit you make. Here's how to stop it.](/blog/claude-code-coauthored-commits/) Anthropic's AI assistant stamps 'Co-authored-by: Claude' onto every commit by default. It's legally meaningless, it's opt-out rather than opt-in, and it's not there for you. Here's how to turn it off. - 06/23 [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. - 06/22 [Why I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. --- ## Tagged "performance" URL: https://alex.zappa.dev/tags/performance Posts tagged with #performance. # Tagged "performance" ## 2023 - 08/13 [Migrating from WordPress to 11ty: enhancing website performance and control](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) Discover the benefits of using 11ty for static website generation, including improved performance, simplicity, and control over your website. Take your online presence to new heights with 11ty. --- ## Tagged "personal" URL: https://alex.zappa.dev/tags/personal Posts tagged with #personal. # Tagged "personal" ## 2025 - 12/28 [2025 year in review: a year of building, learning, and gratitude](/blog/year-in-review-2025/) Reflecting on a year filled with code, open-source contributions, and the amazing people who made it all worthwhile. --- ## Tagged "photo" URL: https://alex.zappa.dev/tags/photo Posts tagged with #photo. # Tagged "photo" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "php" URL: https://alex.zappa.dev/tags/php Posts tagged with #php. # Tagged "php" ## 2016 - 11/08 [Adding subscribers to a list using Mailchimp’s API v3](/blog/adding-subscribers-to-a-list-using-mailchimps-api-v3/) Learn how to add and update list members in MailChimp's API v3 using a PUT request. Includes code example and tips. May the 4th be with you! --- ## Tagged "picsum" URL: https://alex.zappa.dev/tags/picsum Posts tagged with #picsum. # Tagged "picsum" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "pixabay" URL: https://alex.zappa.dev/tags/pixabay Posts tagged with #pixabay. # Tagged "pixabay" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "placehold" URL: https://alex.zappa.dev/tags/placehold Posts tagged with #placehold. # Tagged "placehold" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "placeholder" URL: https://alex.zappa.dev/tags/placeholder Posts tagged with #placeholder. # Tagged "placeholder" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "plugin" URL: https://alex.zappa.dev/tags/plugin Posts tagged with #plugin. # Tagged "plugin" ## 2019 - 04/21 [WP plugin Elementor Multidomain Support](/blog/wp-plugin-elementor-multidomain-support/) A plugin for Elementor Page Builder that adds multi-domain support for Multilanguage websites. Available on WordPress repository and GitHub. - 01/19 [Campaign URL Builder Introducing the Shortcodes](/blog/campaign-url-builder-introducing-the-shortcodes/) Introducing the first release of shortcodes for Campaign URL Builder plugin. Shortcodes can be customized with various attributes and can generate forms on the front-end. Bitly API key is required. Shortcodes can be styled and have optional parameters. Examples provided for simple, advanced, and PHP usage. - 01/14 [Transitioning Google URL shortener to Bitly](/blog/transitioning-google-url-shortener-to-bitly/) Campaign URL Builder plugin is moving to Bitly API endpoint; support for goo.gl URL shortener will be turned down in March 2019. Previously created links will redirect. - 01/01 [WP plugin Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) Learn how to track Custom Campaigns in Google Analytics with this plugin. Easily create short links and share them on social networks. Get and install the plugin for WordPress from the repository or GitHub. Use the shortcode to generate shortlinks on any page. May the 4th be with you! --- ## Tagged "posts" URL: https://alex.zappa.dev/tags/posts Posts tagged with #posts. # Tagged "posts" ## 2026 - 03/10 [HubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) HubSpot's cookie consent banner secretly loads Google Fonts before you even consent, leaking your IP to Google. Here's how I fixed it with a JavaScript proxy hack. - 01/11 [Migrating from Cloudflare Pages to Workers (and cleaning up the mess)](/blog/cloudflare-pages-to-workers-migration/) My experience merging an 11ty static site and API into a single Cloudflare Worker, plus a bash script to delete old Pages deployments when the dashboard won't let you. - 01/03 [How I replaced my ISP router and extracted PPPoE credentials using a bridge sniffer](/blog/isp-router-replacement/) A guide to replacing your Digi España router with your own hardware by capturing PPPoE credentials through packet sniffing. Includes bridge setup, Wireshark analysis, and GL.iNet configuration. --- ## Tagged "privacy" URL: https://alex.zappa.dev/tags/privacy Posts tagged with #privacy. # Tagged "privacy" ## 2026 - 03/10 [HubSpot's Cookie Banner Loads Google Fonts - And Nobody Seems to Care](/blog/hubspot-cookie-banner-google-fonts-gdpr/) HubSpot's cookie consent banner secretly loads Google Fonts before you even consent, leaking your IP to Google. Here's how I fixed it with a JavaScript proxy hack. --- ## Tagged "responsive-images" URL: https://alex.zappa.dev/tags/responsive-images Posts tagged with #responsive-images. # Tagged "responsive-images" ## 2021 - 10/27 [Mobile friendly Responsive images with CSS](/blog/mobile-friendly-responsive-images-with-css/) Learn how to make responsive images in HTML and CSS. Discover how to prevent images from going outside the container. Find solutions for WordPress and HubSpot. --- ## Tagged "schema-markup" URL: https://alex.zappa.dev/tags/schema-markup Posts tagged with #schema-markup. # Tagged "schema-markup" ## 2024 - 01/07 [How to use structured data (schema markup) in HubSpot to improve SEO](/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo/) How to use Structured Data, also referred to as Schema Markup, inside of HubSpot that is populated using HubL variables for use to improve SEO. --- ## Tagged "seo" URL: https://alex.zappa.dev/tags/seo Posts tagged with #seo. # Tagged "seo" ## 2025 - 10/08 [Guest post on Orange Marketing: AIEO is the new SEO](/blog/aieo-orange-guest/) The future of search isn't keywords, it's context. In my guest post for Orange Marketing, I explored how AI is reshaping SEO and how marketers and web developers can adapt. ## 2024 - 01/07 [How to use structured data (schema markup) in HubSpot to improve SEO](/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo/) How to use Structured Data, also referred to as Schema Markup, inside of HubSpot that is populated using HubL variables for use to improve SEO. --- ## Tagged "shortcodes" URL: https://alex.zappa.dev/tags/shortcodes Posts tagged with #shortcodes. # Tagged "shortcodes" ## 2019 - 01/19 [Campaign URL Builder Introducing the Shortcodes](/blog/campaign-url-builder-introducing-the-shortcodes/) Introducing the first release of shortcodes for Campaign URL Builder plugin. Shortcodes can be customized with various attributes and can generate forms on the front-end. Bitly API key is required. Shortcodes can be styled and have optional parameters. Examples provided for simple, advanced, and PHP usage. --- ## Tagged "simplicity" URL: https://alex.zappa.dev/tags/simplicity Posts tagged with #simplicity. # Tagged "simplicity" ## 2023 - 08/13 [Migrating from WordPress to 11ty: enhancing website performance and control](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) Discover the benefits of using 11ty for static website generation, including improved performance, simplicity, and control over your website. Take your online presence to new heights with 11ty. --- ## Tagged "ssh" URL: https://alex.zappa.dev/tags/ssh Posts tagged with #ssh. # Tagged "ssh" ## 2024 - 12/06 [How to manage multiple SSH keys for multiple GitHub accounts on one computer](/blog/how-to-manage-multiple-ssh-keys-for-multiple-github-accounts-on-one-computer/) Learn how to set up and manage multiple SSH keys on a single computer to seamlessly work with multiple GitHub accounts. Step-by-step guide for developers. --- ## Tagged "stock" URL: https://alex.zappa.dev/tags/stock Posts tagged with #stock. # Tagged "stock" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "structured-data" URL: https://alex.zappa.dev/tags/structured-data Posts tagged with #structured-data. # Tagged "structured-data" ## 2024 - 01/07 [How to use structured data (schema markup) in HubSpot to improve SEO](/blog/how-to-use-structured-data-schema-markup-in-hubspot-to-improve-seo/) How to use Structured Data, also referred to as Schema Markup, inside of HubSpot that is populated using HubL variables for use to improve SEO. --- ## Tagged "tailwind" URL: https://alex.zappa.dev/tags/tailwind Posts tagged with #tailwind. # Tagged "tailwind" ## 2026 - 06/23 [Astro 7 dropped the day after I finished my Astro 6 migration](/blog/astro7-update/) The universe has a sense of humor. I published my migration story, went to sleep, and woke up to Astro 7. Here's what broke, what I fixed, and why I'm not even mad. --- ## Tagged "tools" URL: https://alex.zappa.dev/tags/tools Posts tagged with #tools. # Tagged "tools" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. --- ## Tagged "tracking" URL: https://alex.zappa.dev/tags/tracking Posts tagged with #tracking. # Tagged "tracking" ## 2021 - 10/22 [Keep Track UTM parameters for HubSpot forms like Cookie Monster](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/) Track UTMs and store them using cookies instead of sessionStorage. Use JavaScript Cookie library and add the code to the website footer. - 10/16 [Track UTM parameters for HubSpot forms like a BOSS](/blog/track-utm-parameters-for-hubspot-forms-like-a-boss/) Learn how to create and track marketing campaigns by using tracking URLs with HubSpot. Pass UTM parameters to hidden form fields using JavaScript. Store and feed form fields in sessionStorage. Install the code in the site footer of your HubSpot website. --- ## Tagged "ubuntu" URL: https://alex.zappa.dev/tags/ubuntu Posts tagged with #ubuntu. # Tagged "ubuntu" ## 2019 - 04/14 [How to block visitors by country with the NGINX GeoIP Module (Debian/Ubuntu)](/blog/how-to-block-visitors-by-country-with-the-nginx-geoip-module-debianubuntu/) Block visitors by country using NGINX and the GeoIP module. Install the module and database, configure the nginx.conf file, and test the configuration before restarting nginx. Instructions with examples provided. --- ## Tagged "unsplash" URL: https://alex.zappa.dev/tags/unsplash Posts tagged with #unsplash. # Tagged "unsplash" ## 2023 - 08/20 [The best free image placeholder for quick and easy content mockups](/blog/the-best-free-image-placeholder-for-quick-and-easy-content-mockups/) Find high-quality, visually appealing images for articles or blog posts with free image placeholders like Placehold.co, Lorem Picsum, Unsplash, and Pixabay Fillers. Customize size, background, text, and more. Streamline your content creation workflow and engage your audience. --- ## Tagged "user-management" URL: https://alex.zappa.dev/tags/user-management Posts tagged with #user-management. # Tagged "user-management" ## 2024 - 04/09 [Custom user avatars for WordPress](/blog/custom-user-avatars-for-wordpress/) Adding custom avatar functionality to your WordPress site. Simple and quick solutions to make your website even more attractive to users! --- ## Tagged "utm" URL: https://alex.zappa.dev/tags/utm Posts tagged with #utm. # Tagged "utm" ## 2021 - 10/22 [Keep Track UTM parameters for HubSpot forms like Cookie Monster](/blog/keep-track-utm-parameters-for-hubspot-forms-like-cookie-monster/) Track UTMs and store them using cookies instead of sessionStorage. Use JavaScript Cookie library and add the code to the website footer. - 10/16 [Track UTM parameters for HubSpot forms like a BOSS](/blog/track-utm-parameters-for-hubspot-forms-like-a-boss/) Learn how to create and track marketing campaigns by using tracking URLs with HubSpot. Pass UTM parameters to hidden form fields using JavaScript. Store and feed form fields in sessionStorage. Install the code in the site footer of your HubSpot website. --- ## Tagged "vpn" URL: https://alex.zappa.dev/tags/vpn Posts tagged with #vpn. # Tagged "vpn" ## 2022 - 01/21 [How to change App Store preferred Language 2022](/blog/how-to-change-app-store-preferred-language-2022/) Resolve your AppStore language issue on macOS! Despite numerous solutions, I discovered a simple fix for the problem. Change your VPN to UK location, log out of AppStore, force close it, and open again. After logging in, AppStore will switch to English. --- ## Tagged "wavebox" URL: https://alex.zappa.dev/tags/wavebox Posts tagged with #wavebox. # Tagged "wavebox" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. --- ## Tagged "web" URL: https://alex.zappa.dev/tags/web Posts tagged with #web. # Tagged "web" ## 2026 - 06/22 [Why I migrated my stack from Eleventy to Astro 6](/blog/astro6-migration/) Two years on a DIY toolchain, an acquisition that changed the vibe, and why moving FreshJuice to Astro 6 felt like switching from Arch Linux to macOS. --- ## Tagged "web-archiving" URL: https://alex.zappa.dev/tags/web-archiving Posts tagged with #web-archiving. # Tagged "web-archiving" ## 2024 - 12/01 [webChronicle: your personal Wayback Machine](/blog/webchronicle/) A web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. --- ## Tagged "webchronicle" URL: https://alex.zappa.dev/tags/webchronicle Posts tagged with #webchronicle. # Tagged "webchronicle" ## 2024 - 12/01 [webChronicle: your personal Wayback Machine](/blog/webchronicle/) A web archiving tool that allows you to capture and explore snapshots of webpages over time. Like the Wayback Machine, but as your own personal Time Machine. --- ## Tagged "wordpress" URL: https://alex.zappa.dev/tags/wordpress Posts tagged with #wordpress. # Tagged "wordpress" ## 2024 - 04/09 [Custom user avatars for WordPress](/blog/custom-user-avatars-for-wordpress/) Adding custom avatar functionality to your WordPress site. Simple and quick solutions to make your website even more attractive to users! ## 2023 - 08/13 [Migrating from WordPress to 11ty: enhancing website performance and control](/blog/migrating-from-wordpress-to-11ty-enhancing-website-performance-and-control/) Discover the benefits of using 11ty for static website generation, including improved performance, simplicity, and control over your website. Take your online presence to new heights with 11ty. ## 2019 - 04/21 [WP plugin Elementor Multidomain Support](/blog/wp-plugin-elementor-multidomain-support/) A plugin for Elementor Page Builder that adds multi-domain support for Multilanguage websites. Available on WordPress repository and GitHub. - 01/19 [Campaign URL Builder Introducing the Shortcodes](/blog/campaign-url-builder-introducing-the-shortcodes/) Introducing the first release of shortcodes for Campaign URL Builder plugin. Shortcodes can be customized with various attributes and can generate forms on the front-end. Bitly API key is required. Shortcodes can be styled and have optional parameters. Examples provided for simple, advanced, and PHP usage. - 01/14 [Transitioning Google URL shortener to Bitly](/blog/transitioning-google-url-shortener-to-bitly/) Campaign URL Builder plugin is moving to Bitly API endpoint; support for goo.gl URL shortener will be turned down in March 2019. Previously created links will redirect. - 01/01 [WP plugin Campaign URL Builder](/blog/wp-plugin-campaign-url-builder/) Learn how to track Custom Campaigns in Google Analytics with this plugin. Easily create short links and share them on social networks. Get and install the plugin for WordPress from the repository or GitHub. Use the shortcode to generate shortlinks on any page. May the 4th be with you! --- ## Tagged "year-in-review" URL: https://alex.zappa.dev/tags/year-in-review Posts tagged with #year-in-review. # Tagged "year-in-review" ## 2025 - 12/28 [2025 year in review: a year of building, learning, and gratitude](/blog/year-in-review-2025/) Reflecting on a year filled with code, open-source contributions, and the amazing people who made it all worthwhile. --- ## Tagged "zed" URL: https://alex.zappa.dev/tags/zed Posts tagged with #zed. # Tagged "zed" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. --- ## Tagged "zen" URL: https://alex.zappa.dev/tags/zen Posts tagged with #zen. # Tagged "zen" ## 2026 - 08/14 [I walked away from WaveBox and JetBrains in the same month](/blog/tool-migration-wavebox-jetbrains/) Ten years of WaveBox, JetBrains paid through 2028, and why I switched to Zen and Zed anyway. --- ## Tools & Utilities URL: https://alex.zappa.dev/tools Discover a collection of favorite developer tools. # Tools & Utilities Welcome to a curated collection of my favorite developer tools! In this compilation, you'll find a range of efficient tools that enhance productivity and streamline workflows for developers. These tools include powerful IDEs, terminal replacements, code storage and assistance platforms, website hosting and development environments, supercharged web browsers, git clients, password managers, productivity boosters, and code snippets managers. Join me in exploring these exceptional tools and supercharge your development process! ## JetBrains Developer Tools JetBrains makes some of the best developer tools out there. I use [WebStorm](https://www.jetbrains.com/webstorm/), [RustRover](https://www.jetbrains.com/rust/), and [PhpStorm](https://www.jetbrains.com/phpstorm/) on a daily basis. JetBrains also makes a great IDE for Go, Ruby, and [many other languages](https://www.jetbrains.com/products/). ## HubSpot DevTools [HubSpot DevTools](https://freshjuice.dev/hubspot-devtools/) is a browser extension I built for Chrome and Firefox. It streamlines HubSpot CMS development with quick toggles for debug mode, cache busting, and backend debugging. Also includes handy shortcuts to HubL docs and Design Manager. Free, [open-source](https://github.com/freshjuice-dev/hubspot-devtools-extension), and collects zero data. ## iTerm2 [iTerm2](https://iterm2.com/) is a replacement for Terminal and the successor to iTerm. It works on Macs with macOS 10.10 or newer. iTerm2 brings the terminal into the modern age with features you never knew you always wanted. ## GitHub with CoPilot Everyone needs a place to store their code. I've use Bitbucket, and [GitLab](https://gitlab.com/reatlat) in the past, but went all-in on [GitHub](https://github.com/reatlat). It's the primary store for all my personal and private projects, and I make sure that I have local backups with [gickup](https://github.com/cooperspencer/gickup). [GitHub CoPilot](https://github.com/features/copilot) is an AI pair programmer that helps you write better code. It's a great tool for learning new languages, and for getting started with new projects. ## Cloudflare Pages The majority of sites that I build today use [Eleventy](https://11ty.dev/), and are hosted by [Cloudflare Pages](https://pages.cloudflare.com/). It's a great combination, and I've been able to build some really cool sites with it. ## CodePen [CodePen](https://codepen.io/reatlat) is a social development environment for front-end designers and developers. Build and deploy a website, show off your work, build test cases to learn and debug, and find inspiration. ## WaveBox [WaveBox](https://wavebox.io/) is more than just a browser. It's a desktop app that brings together all your web apps into one place. It's a supercharged web browser with tabbed windows, notifications, badges, and workspaces. I use WaveBox since first day it was released. WaveBox is successor to [WMail](https://thomas101.github.io/wmail/), which was a great desktop client for Gmail. WaveBox is a great way to keep all of my work and personal email accounts in one place. ## Zen Browser [Zen Browser](https://zen-browser.app/) is a beautifully designed, privacy-focused browser built on Firefox. I use it for front-end development because of its clean interface, excellent DevTools inherited from Firefox, and strong privacy defaults. It's fast, minimal, and doesn't get in the way of my workflow. ## Fork [Fork](https://git-fork.com/) is a fast and friendly git client for Mac and Windows. Fork is getting better and better every day and we are happy to share our results with you. ## 1Password [1Password](https://1password.com/) is a password manager that keeps you safe online. It's the only way to keep your accounts safe from password reuse, phishing, and other online threats. I'm a long time 1Password user, and have managed to convince all of my immediate family to use it too. Shared vaults are great for tech support, and they allow my wife, son and I to keep track of documents that we need. Autocomplete works great, and it's the single source of truth for all of my software licenses and club memberships. I couldn't live without it. ## Alfred [Alfred](https://www.alfredapp.com/) is an award-winning app for macOS which boosts your efficiency with hotkeys, keywords, text expansion and more. Search your Mac and the web, and be more productive with custom actions to control your Mac. I started out using Alfred as a Spotlight replacement many years ago. Somewhere along the way I picked up a lifetime Mega supporter license and use it every day. Most of the time it's a quick application launcher, but I have a couple of downloaded workflows (such as the emoji picker), and some custom workflows for navigating to specific work systems quickly. I've recently started using it's snippets capability, both for common replies and for demo purposes (with autoexpand enabled). ## SnippetsLab [SnippetsLab](https://www.renfei.org/snippets-lab/) is a full-featured, easy-to-use snippets manager. It helps you to build your personal code snippets library and makes sure that you have easy access to them whenever you want. Sync via iCloud Drive across Mac, iPad, and iPhone. ## Hazel [Hazel](https://www.noodlesoft.com/) is a housekeeper for your folders and files. It's a powerful tool that helps you to organize your files according to rules you create. Hazel can also manage your trash and uninstall your applications.