<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="/rss.xsl" media="all"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Roastidio.us Tagged with web</title>
<link>https://roastidio.us/tag/2691</link>
<atom:link href="https://roastidio.us/tagged_with/web" rel="self" type="application/rss+xml"></atom:link>
<description>Roastidio.us Tagged with web</description>
<item>
<title>A Linux-like kernel in a browser tab - deep dive in the BrowserPod architecture</title>
<link>https://labs.leaningtech.com/blog/browserpod-deep-dive.html</link>
<guid isPermaLink="false">UqzdZlekwjhSmSRRZ6pWfxzRMa_eIgYBdVOL-w==</guid>
<pubDate>Mon, 18 May 2026 17:06:25 +0000</pubDate>
<description>Comments</description>
<content:encoded>&lt;p&gt;&lt;a href=&quot;https://lobste.rs/s/s3wqhi/linux_like_kernel_browser_tab_deep_dive&quot;&gt;Comments&lt;/a&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Cross-Document View Transitions: The Gotchas Nobody Mentions | CSS-Tricks</title>
<link>https://css-tricks.com/cross-document-view-transitions-part-1/</link>
<enclosure type="image/jpeg" length="0" url="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/06/loader-dots.jpg"></enclosure>
<guid isPermaLink="false">fycIuTxttZI4nP1aUg-Zf9HP4dTPvmeH2i9Xqw==</guid>
<pubDate>Mon, 18 May 2026 17:06:25 +0000</pubDate>
<description>This is Part 1 of a two-part series about cross-document view transitions, going over all the gotchas, from ditching the deprecated way to opt into them to a little-known 4-second timeout.</description>
<content:encoded>&lt;p&gt;I wasted an entire Saturday on this.&lt;/p&gt;&lt;p&gt;Not a lazy Saturday either, but one of those rare, carved-out, “I’m finally going to build that thing” Saturdays. I’d seen &lt;a href=&quot;https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/&quot;&gt;Jake Archibald’s demos&lt;/a&gt;. I’d watched the Chrome Dev Summit talk. I knew cross-document view transitions were real, that you could get those &lt;a href=&quot;https://css-tricks.com/native-like-animations-for-page-transitions-on-the-web/&quot;&gt;slick native-feeling page transitions on plain old multi-page sites&lt;/a&gt; without a single framework. No React. No Astro. No client-side router pretending your multi-page application (MPA) is single-page application (SPA). Just HTML pages linking to other HTML pages, with the browser handling the animation between them. Hell yes.&lt;/p&gt;&lt;p&gt;So I started building. And nothing worked.&lt;/p&gt;&lt;p&gt;The first tutorial I found had me dropping &lt;code&gt;&amp;lt;meta name=&amp;quot;view-transition&amp;quot; content=&amp;quot;same-origin&amp;quot;&amp;gt;&lt;/code&gt; into my &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. Seemed simple enough. I added it to both pages, clicked my link, and… nothing. No transition. No error. Just a normal, instant page load like it was 2004. I opened DevTools, double-checked my syntax, restarted the server, tried Chrome Canary, cleared the cache. Nothing. I did what any self-respecting developer does at that point – I copied the code character by character from the blog post and pasted it in. Still nothing.&lt;/p&gt;&lt;p&gt;I spent two hours convinced I was an idiot.&lt;/p&gt;&lt;p&gt;Turns out that &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag syntax? &lt;a href=&quot;https://css-tricks.com/snippets/css/basic-view-transition/&quot;&gt;Deprecated.&lt;/a&gt; Gone. Chrome shipped it, then replaced it with a CSS-based opt-in, and half the internet’s tutorials still show the old way. Those older blog posts still rank well. They look authoritative. And they’re just wrong now. Not wrong because the authors were bad – wrong because the spec moved under everyone’s feet and nobody went back to update their posts.&lt;/p&gt;&lt;p&gt;The other half of the tutorials I found were about same-document view transitions. SPA stuff. &lt;code&gt;document.startViewTransition()&lt;/code&gt; called in JavaScript when you swap DOM content yourself, which is cool and useful but a completely different feature when you actually sit down to implement it. The API surface is different. The mental model is different. The gotchas are &lt;em&gt;very&lt;/em&gt; different. And yet, Google “view transitions tutorial” and good luck figuring out which flavor you’re reading about until you’re three paragraphs deep.&lt;/p&gt;&lt;p&gt;So if you’re here, I’m guessing you’ve been through some version of this. You tried the meta tag. It didn’t work. You tried the JavaScript API on a real multi-page site and realized it only fires within a single document. You maybe got something half-working in a demo but it fell apart the second you added real content â€” images stretching weird, transitions hanging for seconds with no explanation, or your CSS file turning into 200 lines of &lt;a href=&quot;https://css-tricks.com/almanac/properties/v/view-transition-name/&quot;&gt;&lt;code&gt;view-transition-name&lt;/code&gt;&lt;/a&gt; declarations because you have a grid of 40 product cards. You blamed yourself. It wasn’t your fault. The documentation ecosystem around this feature is a mess right now, and the spec has been a moving target.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;This is Part 1 of a two-part series&lt;/strong&gt;, and it’s the article I wish existed on that Saturday. We’re going to cover the actual current way to opt in with &lt;a href=&quot;https://css-tricks.com/almanac/rules/v/view-transition/&quot;&gt;&lt;code&gt;@view-transition&lt;/code&gt;&lt;/a&gt; in CSS (not the meta tag, not JavaScript), then dig into the 4-second timeout that will silently kill your transitions on slow pages and how to debug it, then fix the aspect ratio warping that makes every image-heavy transition look like a fun house mirror, and finally get a proper handle on the &lt;code&gt;pagereveal&lt;/code&gt; and &lt;code&gt;pageswap&lt;/code&gt; events that give you programmatic control over the whole lifecycle.&lt;/p&gt;&lt;p&gt;In Part 2, we’ll tackle the scaling problem – how to handle &lt;code&gt;view-transition-name&lt;/code&gt; across dozens or hundreds of elements without your stylesheet becoming a disaster, the difference between &lt;code&gt;view-transition-name&lt;/code&gt; and &lt;a href=&quot;https://css-tricks.com/almanac/properties/v/view-transition-class/&quot;&gt;&lt;code&gt;view-transition-class&lt;/code&gt;&lt;/a&gt;, just-in-time naming patterns, and doing &lt;a href=&quot;https://css-tricks.com/almanac/rules/m/media/prefers-reduced-motion/&quot;&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt; the right way.&lt;/p&gt;&lt;div&gt;&lt;div&gt;
&lt;h4&gt;Cross-Document View Transitions Series&lt;/h4&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://css-tricks.com/cross-document-view-transitions-part-1/&quot;&gt;The Gotchas Nobody Mentions&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(You are here!)&lt;/em&gt;&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Scaling View Transitions Across Hundreds of Elements&lt;/strong&gt; &lt;em&gt;(Next Monday!)&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Grab coffee. Maybe a refill. This one’s dense and I’m not going to waste your time, but there’s a lot of ground here and none of it is obvious.&lt;/p&gt;&lt;h2&gt;The Old Way is Dead&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;!-- THIS IS DEPRECATED - stop copying this from old tutorials --&amp;gt;
&amp;lt;meta name=&amp;quot;view-transition&amp;quot; content=&amp;quot;same-origin&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;/* THIS is the current opt-in - goes in your CSS */
@view-transition {
  navigation: auto;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here’s the minimal setup. Two HTML files, one CSS rule on each. Note that, as of 2026, cross-document view transitions are supported in Chromium-based browsers and Safari 18.2+. Firefox support is in progress as I’m writing this.&lt;/p&gt;&lt;p&gt;That’s it. Two HTML files. One CSS rule on each. Click a link between them in a supporting browser (like modern Chromium or Safari 18.2+) and you get a smooth cross-fade. No JavaScript. No meta tags. No build step. The browser snapshots the old page, snapshots the new page, and animates between them automatically.&lt;/p&gt;&lt;p&gt;Now, why did the spec move from a meta tag to a CSS at-rule? It wasn’t arbitrary.&lt;/p&gt;&lt;p&gt;The meta tag was a blunt instrument. It was on or off for the entire page. You couldn’t say “enable transitions on desktop but not on mobile where the animations feel janky on low-end hardware.” You couldn’t conditionally opt in based on user preferences. It was just… there, or not.&lt;/p&gt;&lt;p&gt;The CSS approach opens all of that up:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/* Only enable transitions if the user hasn&amp;#39;t asked for reduced motion */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;/* Only enable on viewports wide enough for the animation to feel good */
@media (min-width: 768px) {
  @view-transition {
    navigation: auto;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s a real upgrade. You get the same conditional power you already have with every other CSS feature. Media queries, &lt;a href=&quot;https://css-tricks.com/almanac/rules/s/supports/&quot;&gt;&lt;code&gt;@supports&lt;/code&gt;&lt;/a&gt;, whatever scoping logic you want — it all just works because the opt-in lives where your styles live.&lt;/p&gt;&lt;p&gt;There’s also a subtlety that matters: the CSS rule can be different on the old page versus the new page. Both pages need to opt in for the transition to fire. If Page A has &lt;code&gt;@view-transition { navigation: auto; }&lt;/code&gt; but Page B doesn’t, you get no transition. This is actually useful – it means your 404 page or your login redirect can skip transitions without any JavaScript coordination.&lt;/p&gt;&lt;p&gt;One more thing worth noting here: &lt;code&gt;navigation: auto&lt;/code&gt; only kicks in for user-initiated, same-origin navigations. If the user clicks a regular link or hits the browser’s Back button, you get a transition. But &lt;code&gt;window.location.href = &amp;quot;/somewhere&amp;quot;&lt;/code&gt; set programmatically, or a cross-origin link, or a form submission with a &lt;code&gt;POST&lt;/code&gt;? No transition. The browser is intentionally conservative about when it fires, and honestly that’s the right call. You don’t want a fancy cross-fade on a &lt;code&gt;POST&lt;/code&gt; request that’s creating a payment.&lt;/p&gt;&lt;p&gt;Look, if you’ve been following an outdated tutorial and your transitions just silently don’t work, this is almost certainly why. The meta tag &lt;a href=&quot;https://developer.chrome.com/blog/new-in-chrome-111?hl=en&quot;&gt;shipped in Chrome 111&lt;/a&gt;, got a few months of real-world use, and then the Chrome team deprecated it in favor of the CSS at-rule &lt;a href=&quot;https://developer.chrome.com/release-notes/126&quot;&gt;starting around Chrome 126&lt;/a&gt;. No console warning. No error. The old syntax just quietly does nothing now. Honestly, a deprecation warning in DevTools would’ve saved me (and probably you) a lot of grief, but here we are.&lt;/p&gt;&lt;p&gt;Swap the meta tag for the CSS rule. That’s step one. Everything else in this article builds on it.&lt;/p&gt;&lt;h2&gt;Your Transition Will Randomly Die, and Here’s Why&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;// Drop this in your pages to see what&amp;#39;s actually happening
window.addEventListener(&amp;quot;pagereveal&amp;quot;, (event) =&amp;gt; {
  if (!event.viewTransition) {
    console.log(
      &amp;quot;No view transition - page didn&amp;#39;t opt in or browser skipped it&amp;quot;,
    );
    return;
  } // This is the one that&amp;#39;ll save your sanity

  event.viewTransition.finished
    .then(() =&amp;gt; console.log(&amp;quot;Transition completed ✅&amp;quot;))
    .catch((err) =&amp;gt; {
      // You&amp;#39;ll see &amp;quot;TimeoutError&amp;quot; here and nowhere else
      console.error(&amp;quot;Transition killed:&amp;quot;, err.name, err.message);
    });
});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here’s the thing nobody puts in their blog post: &lt;strong&gt;cross-document view transitions have a hard 4-second timeout.&lt;/strong&gt; If the new page doesn’t reach a state the browser considers “renderable” within 4 seconds of the navigation starting, the transition just… dies. No animation. No cross-fade. The new page snaps in like view transitions don’t exist. And unless you’ve got that &lt;code&gt;pagereveal&lt;/code&gt; listener wired up and your console open, you won’t get any indication that anything went wrong.&lt;/p&gt;&lt;p&gt;Four seconds sounds generous â€” until it isn’t.&lt;/p&gt;&lt;p&gt;Think about what happens on a real site. Your page loads. The HTML arrives, fine, that’s fast. But maybe you’ve got a big hero image that’s render-blocking. Maybe there’s a slow API call that your server waits on before sending the response – a product page hitting an inventory service, a dashboard waiting on analytics data, anything with server-side rendering that actually does work before responding. Maybe you’re on a decent connection but the page has three web fonts loading from Google Fonts with &lt;code&gt;font-display: block&lt;/code&gt;. Any of these can push you past that 4-second window, and the timeout doesn’t care &lt;em&gt;why&lt;/em&gt; you’re slow. It just cuts the transition.&lt;/p&gt;&lt;p&gt;The really maddening part? It works perfectly on localhost. Your dev server responds in 80ms. The transition is butter. You deploy to production, your server’s cold-starting a lambda or your CDN cache missed, and suddenly users get zero transitions on the first click. You can’t reproduce it locally. You start questioning everything.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// You can also catch this on the OLD page using `pageswap`
// Useful for cleanup or logging which navigations fail
window.addEventListener(&amp;quot;pageswap&amp;quot;, (event) =&amp;gt; {
  if (event.viewTransition) {
    event.viewTransition.finished.catch((err) =&amp;gt; {
      // Log it, send it to your analytics, whatever
      console.warn(&amp;quot;Outgoing transition aborted:&amp;quot;, err.name);
    });
  }
});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, what do you actually do about it?&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Option one: make your page faster.&lt;/strong&gt; I know, groundbreaking advice. But seriously – if your cross-document transition is dying, that’s a signal your page load is genuinely slow. The timeout is acting as a performance canary. Look at your Performance tab in DevTools, run a Lighthouse audit (which may &lt;a href=&quot;https://www.smashingmagazine.com/2024/11/why-optimizing-lighthouse-score-not-enough-fast-website/&quot;&gt;not be perfect&lt;/a&gt;), figure out what’s blocking first render. This isn’t view-transition-specific advice, but the timeout forces you to care about it.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Option two is more interesting&lt;/strong&gt;, and it’s the thing I wish I’d known about immediately.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;!-- Note: rel=&amp;quot;expect&amp;quot; is newer and browser support is rolling out --&amp;gt;
&amp;lt;link rel=&amp;quot;expect&amp;quot; href=&amp;quot;#hero&amp;quot; blocking=&amp;quot;render&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;expect&amp;quot; href=&amp;quot;#hero&amp;quot; blocking=&amp;quot;render&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;…tells the browser: “Don’t consider this page renderable until an element matching &lt;code&gt;#hero&lt;/code&gt; is in the DOM.” That sounds like it would make things &lt;em&gt;slower&lt;/em&gt;, and in a way it does – it delays first paint. But for view transitions, that’s exactly what you want: you’re telling the browser to hold the snapshot until the important content is actually there, rather than snapping a screenshot of a half-loaded page or, worse, timing out because some image in the footer is still downloading and blocking something.&lt;/p&gt;&lt;p&gt;It’s a trade-off. You’re choosing a slightly delayed, but smooth, transition over a fast, but-broken, one.&lt;/p&gt;&lt;p&gt;Honestly, the 4-second limit is probably the right call from the browser’s perspective. You don’t want a user clicking a link and staring at a frozen page for 10 seconds while the browser waits to do a fancy animation. At some point, just showing the damn page is better than a pretty transition. But I wish Chrome would surface the timeout more visibly – a DevTools warning, a performance marker, &lt;em&gt;something&lt;/em&gt;. Right now it fails silently and that’s the whole problem.&lt;/p&gt;&lt;p&gt;One more thing worth knowing: the timeout clock starts when navigation begins, not when the new page’s HTML starts arriving. Network latency counts. The Time to First Byte (TTFB) Core Web Vital counts. If your server takes 2 seconds to respond and your page takes 2.5 seconds to render after that, you’re over the limit even though neither half feels slow on its own.&lt;/p&gt;&lt;p&gt;A debugging tip that’s saved me more than once: Chrome’s DevTools has an Animations panel (it’s under “More tools” if you don’t see it) that can actually capture view transitions in action. You can slow them down to 10% speed, replay them, and inspect the pseudo-element tree mid-animation. It’s not obvious that it works for view transitions, but it does. Between that and the &lt;code&gt;pagereveal&lt;/code&gt; listener above, you can diagnose most timeout issues pretty quickly.&lt;/p&gt;&lt;p&gt;Put that &lt;code&gt;pagereveal&lt;/code&gt; listener in early. Watch your console during testing. You’ll thank yourself later.&lt;/p&gt;&lt;h2&gt;Why Your Images Look Like Taffy&lt;/h2&gt;&lt;p&gt;This one’s easier to show with a same-document demo first (since you can actually run it in a single file), but the problem and the fix are identical for cross-document transitions.&lt;/p&gt;&lt;p&gt;Run that. Click the image. Watch the dog turn into silly putty.&lt;/p&gt;&lt;p&gt;The image itself has &lt;code&gt;object-fit: cover&lt;/code&gt; on both sides. The thumbnail looks fine, the hero looks fine. But during the transition? The browser doesn’t transition your &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element. It takes a &lt;em&gt;screenshot&lt;/em&gt; of the old state, takes a screenshot of the new state, and morphs between them. Those screenshots are flat raster images. Your carefully applied &lt;a href=&quot;https://css-tricks.com/almanac/properties/o/object-fit/&quot;&gt;&lt;code&gt;object-fit&lt;/code&gt;&lt;/a&gt;? Gone. The browser is just scaling a bitmap from one box size to another, and when a 150Ã—150 square gets stretched into a 600Ã—300 rectangle, you get taffy.&lt;/p&gt;&lt;p&gt;Here’s the fix:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/* THE FIX - target the transition pseudo-elements directly */
::view-transition-old(hero-img),
::view-transition-new(hero-img) {
  /* Treat the snapshot like an image in a container - crop, don&amp;#39;t stretch */
  object-fit: cover;
  overflow: hidden;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s the whole thing. Two properties on two pseudo-elements.&lt;/p&gt;&lt;p&gt;What’s actually happening: the browser generates a tree of pseudo-elements for every named transition. For an element with &lt;code&gt;view-transition-name: hero-img&lt;/code&gt;, you get this structure during the animation:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;::view-transition
└── ::view-transition-group(hero-img)
    ├── ::view-transition-old(hero-img)
    └── ::view-transition-new(hero-img)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;::view-transition-group&lt;/code&gt; smoothly animates its width and height from the old dimensions to the new ones. That’s the morphing rectangle you see. Inside it, the &lt;code&gt;old&lt;/code&gt; and &lt;code&gt;new&lt;/code&gt; pseudo-elements hold the actual bitmap snapshots, and by default they’re set to &lt;code&gt;object-fit: fill&lt;/code&gt; – meaning “stretch to fill whatever box you’re in, aspect ratio be damned.”&lt;/p&gt;&lt;p&gt;Switching to &lt;code&gt;object-fit: cover&lt;/code&gt; tells those snapshots to maintain their aspect ratio and crop the overflow instead. Same mental model as a background image with &lt;code&gt;background-size: cover&lt;/code&gt;. The transition still animates the box from square to rectangle (or whatever your shapes are), but the image inside crops gracefully instead of warping.&lt;/p&gt;&lt;p&gt;You could also use &lt;code&gt;object-fit: contain&lt;/code&gt; here if you’d rather see the full image with letterboxing instead of cropping. It depends on what looks right for your content. But &lt;code&gt;cover&lt;/code&gt; is what you’ll want 90% of the time, especially for product images and hero shots.&lt;/p&gt;&lt;p&gt;For cross-document transitions, the CSS is identical – you just put it in both pages’ stylesheets:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/* This works cross-document. Same selectors, same fix. */
/* Put it in your shared CSS file that both pages load. */
@view-transition {
  navigation: auto;
}

::view-transition-old(hero-img),
::view-transition-new(hero-img) {
  object-fit: cover;
}

/* You can also control the animation timing on the group */
::view-transition-group(hero-img) {
  animation-duration: 0.4s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Honestly, I think &lt;code&gt;object-fit: cover&lt;/code&gt; should be the &lt;em&gt;default&lt;/em&gt; on these pseudo-elements instead of &lt;code&gt;fill&lt;/code&gt;. I get why the spec chose &lt;code&gt;fill&lt;/code&gt; – it’s predictable, it matches what &lt;code&gt;object-fit&lt;/code&gt; defaults to on replaced elements everywhere else in CSS – but in practice, how often do you actually &lt;em&gt;want&lt;/em&gt; a stretched bitmap during a transition? Almost never. You’ll be adding this override on basically every image transition you build.&lt;/p&gt;&lt;p&gt;One more variant that’s useful when the aspect ratios are wildly different – say a tall portrait thumbnail transitioning into a cinematic widescreen hero:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/* Fine-tune where the crop happens on each side of the transition */
::view-transition-group(hero-img) {
  overflow: hidden;
  border-radius: 8px; /* keep it pretty mid-flight */
}

::view-transition-old(hero-img) {
  object-fit: cover;
  object-position: center center;
}

::view-transition-new(hero-img) {
  object-fit: cover;
  object-position: center top; /* keep the top of the hero visible */
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can set different &lt;code&gt;object-position&lt;/code&gt; values on old versus new, which lets you control where the crop happens on each side of the transition independently. The old thumbnail might look best cropped from center. The new hero might need to anchor to the top. Mix and match.&lt;/p&gt;&lt;p&gt;This took me an embarrassingly long time to figure out. The fix is two lines of CSS, but if you don’t know the pseudo-element tree exists, you don’t even know what to target. Now you do.&lt;/p&gt;&lt;h2&gt;The Two Events That Tie it All Together&lt;/h2&gt;&lt;p&gt;You’ve already seen &lt;code&gt;pagereveal&lt;/code&gt; and &lt;code&gt;pageswap&lt;/code&gt; show up in the code above, but let’s take a step back and talk about what they actually are. Understanding these two events is going to be important, because in Part 2 we’ll lean on them heavily for the just-in-time naming pattern that makes view transitions actually scale.&lt;/p&gt;&lt;p&gt;Cross-document view transitions happen across two pages that have no JavaScript connection to each other. Page A doesn’t know about Page B’s DOM. Since the old and new pages have no way to communicate directly, these events are your only way to coordinate the transition on both sides. Page B didn’t exist when Page A was running. So how do you coordinate anything? How do you decide which elements to name, or customize the transition based on where the user is heading?&lt;/p&gt;&lt;p&gt;That’s what these two events are for. They’re your hooks into the transition lifecycle, one on each side of the navigation.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;pageswap&lt;/code&gt;&lt;/strong&gt; fires on the &lt;strong&gt;outgoing page&lt;/strong&gt;, right before it gets replaced. This is your last chance to touch the old page’s DOM before the browser snapshots it. The event gives you two key properties:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;event.viewTransition&lt;/code&gt;:&lt;/strong&gt; the ViewTransition object for this navigation, or &lt;code&gt;null&lt;/code&gt; if no transition is happening.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;&lt;code&gt;event.activation&lt;/code&gt;:&lt;/strong&gt; a NavigationActivation object that tells you &lt;em&gt;where the user is going&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;That &lt;code&gt;activation&lt;/code&gt; property is the really useful one. &lt;code&gt;event.activation.entry.url&lt;/code&gt; gives you the destination URL, and &lt;code&gt;event.activation.navigationType&lt;/code&gt; tells you whether it’s a &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;traverse&lt;/code&gt; (back/forward), or &lt;code&gt;reload&lt;/code&gt;. This means you can customize the outgoing side of the transition based on the destination. On a product listing page, for example, you can check which product the user clicked, find the matching card, and assign a &lt;code&gt;view-transition-name&lt;/code&gt; to just that element right before the snapshot happens.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;pagereveal&lt;/code&gt;&lt;/strong&gt; fires on the &lt;strong&gt;incoming page&lt;/strong&gt;, right after the page becomes active but while the transition is still running. This is your chance to set up the new side. The event gives you:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;event.viewTransition&lt;/code&gt;:&lt;/strong&gt; same deal, the ViewTransition object or &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;On the incoming page, you check where the user came &lt;em&gt;from&lt;/em&gt; using &lt;code&gt;navigation.activation.from.url&lt;/code&gt; (via the Navigation API), and you read the current URL from &lt;code&gt;window.location&lt;/code&gt;. Between those two pieces of information, you know exactly what kind of navigation just happened and can set up the incoming page’s transition elements accordingly.&lt;/p&gt;&lt;p&gt;Here’s the full lifecycle in order:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;User clicks a link on Page A.&lt;/li&gt;



&lt;li&gt;&lt;code&gt;pageswap&lt;/code&gt; fires on Page A. This is your window to name elements and customize outgoing state.&lt;/li&gt;



&lt;li&gt;Browser snapshots the old page (capturing any named elements).&lt;/li&gt;



&lt;li&gt;Navigation happens, new page loads.&lt;/li&gt;



&lt;li&gt;&lt;code&gt;pagereveal&lt;/code&gt; fires on Page B. You can name elements, customize incoming state.&lt;/li&gt;



&lt;li&gt;Browser snapshots the new page.&lt;/li&gt;



&lt;li&gt;Transition animates between the two snapshots.&lt;/li&gt;



&lt;li&gt;&lt;code&gt;viewTransition.finished&lt;/code&gt; resolves (or rejects) on both sides.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Three things to keep in mind with these events:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;First, always guard with &lt;code&gt;if (!event.viewTransition) return&lt;/code&gt; at the top of your handlers.&lt;/strong&gt; &lt;code&gt;pagereveal&lt;/code&gt; actually fires on &lt;em&gt;every&lt;/em&gt; navigation – initial page load, back/forward, the works – not just view transitions. If there’s no transition happening, &lt;code&gt;event.viewTransition&lt;/code&gt; will be &lt;code&gt;null&lt;/code&gt;, and your handler should bail out gracefully. These handlers are transition sugar, not application logic. Never put side effects in them that you &lt;em&gt;need&lt;/em&gt; for the page to work.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Second, &lt;code&gt;pageswap&lt;/code&gt; only fires if the old page opted into view transitions and the navigation is same-origin.&lt;/strong&gt; If the user middle-clicks to open in a new tab, or the navigation goes cross-origin, the event either won’t fire or &lt;code&gt;event.viewTransition&lt;/code&gt; will be &lt;code&gt;null&lt;/code&gt;. That’s fine, your guard clause handles it.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Third, and this is easy to overlook:&lt;/strong&gt; both events give you access to &lt;code&gt;viewTransition.finished&lt;/code&gt;, which is a promise that resolves when the transition completes or rejects if something goes wrong (like a timeout). Always use this for cleanup, as in removing &lt;code&gt;view-transition-name&lt;/code&gt; values you set dynamically, resetting state, whatever. Stale names from a previous transition will ruin your next one.&lt;/p&gt;&lt;p&gt;We’ve been using these events lightly so far – a &lt;code&gt;pagereveal&lt;/code&gt; listener to catch timeouts, a &lt;code&gt;pageswap&lt;/code&gt; listener for logging. In Part 2 of this little series, they become the backbone of the whole scaling strategy. Stay tuned.&lt;/p&gt;&lt;h2&gt;What’s Next&lt;/h2&gt;&lt;p&gt;That covers the three gotchas that’ll bite you first: the deprecated meta tag that silently does nothing, the 4-second timeout that kills transitions without telling you, and the image distortion that turns every aspect ratio change into a fun house mirror. Plus the two events that give you hooks into the whole lifecycle.&lt;/p&gt;&lt;p&gt;In Part 2, we’ll tackle the scaling problem. When you’ve got a grid of 48 product cards and each one needs a unique &lt;code&gt;view-transition-name&lt;/code&gt;, how do you keep your CSS from exploding? The answer involves &lt;code&gt;view-transition-class&lt;/code&gt; (which is different from &lt;code&gt;view-transition-name&lt;/code&gt; in ways that aren’t obvious), a just-in-time naming pattern using the &lt;code&gt;pageswap&lt;/code&gt; and &lt;code&gt;pagereveal&lt;/code&gt; events we just covered. And one critical note: we’ll cover &lt;code&gt;prefers-reduced-motion&lt;/code&gt; in Part 2, but if you take nothing else from this series, take this: animations can literally make people physically nauseous. Always check that preference and respect it.&lt;/p&gt;&lt;p&gt;The gotchas are behind you. Now it’s time to make it scale.&lt;/p&gt;&lt;div&gt;&lt;div&gt;
&lt;h4&gt;Cross-Document View Transitions Series&lt;/h4&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://css-tricks.com/cross-document-view-transitions-part-1/&quot;&gt;The Gotchas Nobody Mentions&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(You are here!)&lt;/em&gt;&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Scaling View Transitions Across Hundreds of Elements&lt;/strong&gt; &lt;em&gt;(Next Monday!)&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;&lt;/div&gt;</content:encoded>
</item>
<item>
<title>12 tendencias UX/UI de 2026 que ya tienen coste para tu negocio</title>
<link>https://inprofit.eu/web/tendencias-ux-ui-2026/</link>
<guid isPermaLink="false">fWJw2yz0mcg51DoadMaUk60li9cOhFXM2G_GpQ==</guid>
<pubDate>Mon, 18 May 2026 12:19:56 +0000</pubDate>
<description>El UX/UI de 2026 no es una cuestión de estética: cada tendencia tiene consecuencias económicas, regulatorias o competitivas medibles. Estas son las 12 que ningún directivo debería ignorar este año. La entrada 12 tendencias UX/UI de 2026 que ya tienen coste para tu negocio se publicó primero en Inprofit.</description>
<content:encoded>&lt;p&gt;El diseño de interfaces dejó de ser un asunto del departamento de producto. En 2026, cada decisión de UX/UI tiene un correlato directo en conversión, en regulación o en cuota de mercado. Las empresas que siguen tratando el diseño como una capa estética —algo que se decide cuando «ya está el producto»— están pagando esa confusión en euros contantes y sonantes. Estas son las 12 tendencias UX/UI de 2026 que todo directivo necesita entender, no como modas visuales, sino como palancas de negocio.&lt;/p&gt;&lt;h2&gt;1. Generative UI: la interfaz que se rediseña sola&lt;/h2&gt;&lt;p&gt;La Interfaz de Usuario Generativa (GenUI) es el salto más radical de los últimos años en diseño UX 2026: sistemas que no solo adaptan el contenido, sino que reestructuran el layout completo en tiempo real según el contexto del usuario. No es personalización de contenido. Es personalización de la arquitectura de la pantalla.&lt;/p&gt;&lt;p&gt;Gartner estima que para finales de 2026 más del 80% de los productos digitales maduros habrán incorporado alguna forma de IA generativa en la capa de interfaz. Las empresas que lo implementan bien reportan mejoras de conversión de entre el 15% y el 25%. El error habitual: usar GenUI para mostrar más cosas al usuario, en lugar de mostrar menos cosas mejores.&lt;/p&gt;&lt;h2&gt;2. Agentes de IA como nuevos usuarios: diseña para máquinas que navegan por ti&lt;/h2&gt;&lt;p&gt;Hay algo que la mayoría de los equipos de diseño no han asimilado todavía: sus interfaces las navegan cada vez más &lt;a href=&quot;https://inprofit.eu/marketing-lab/automatizacion-de-paginas-web/&quot;&gt;agentes automatizados&lt;/a&gt;, no personas. Asistentes de IA que compran, reservan, comparan y gestionan en nombre de sus usuarios. Diseñar solo para el ojo humano ya es diseñar a medias.&lt;/p&gt;&lt;p&gt;Un estudio publicado en 2026 por IEEE demuestra que los dark patterns son efectivos en más del 70% de los casos cuando el usuario es un agente de IA, frente al 31% cuando se trata de humanos. Esto tiene dos lecturas: si los utilizas, te expones a sanciones regulatorias crecientes; si no los usas, tienes una ventaja competitiva real frente a sitios que todavía los incluyen.&lt;/p&gt;&lt;h2&gt;3. Diseño espacial sin necesidad de gafas&lt;/h2&gt;&lt;p&gt;La estética del spatial design —profundidad, capas, sombras con volumen, elementos que «flotan»— ha salido de los headsets de Apple Vision Pro y Meta Quest para instalarse en pantallas convencionales. Es una respuesta al agotamiento visual del flat design extremo: los usuarios perciben interfaces con materialidad como más confiables y más premium.&lt;/p&gt;&lt;p&gt;No es necesario tener un producto de realidad aumentada para adoptarlo. Se aplica en &lt;a href=&quot;https://inprofit.eu/tendencias-marketing/inteligencia-artificial-predictiva-para-la-ux-web-en-tiempo-real/&quot;&gt;composiciones web&lt;/a&gt;, dashboards y apps móviles con jerarquías visuales tridimensionales que comunican prioridad sin necesidad de texto adicional.&lt;/p&gt;&lt;h2&gt;4. Dark patterns bajo fuego regulatorio: el coste ya es real&lt;/h2&gt;&lt;p&gt;El 97% de las apps más populares en la UE contienen al menos un dark pattern, según datos de 2026. El Reglamento de Servicios Digitales (DSA) en su Artículo 25 prohíbe explícitamente las interfaces manipuladoras, con sanciones de hasta el 6% de los ingresos globales. La aplicación efectiva de multas comenzó en el primer trimestre de 2026.&lt;/p&gt;&lt;p&gt;Esto ya no es un debate ético. Es un riesgo de compliance que los departamentos legal y financiero deben tener sobre la mesa. Los equipos de diseño necesitan auditorías de dark patterns igual que los equipos de desarrollo hacen auditorías de seguridad.&lt;/p&gt;&lt;h2&gt;5. Hiperpersonalización dinámica: del segmento al individuo&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://inprofit.eu/360-marketing/personalizacion-ux-en-marketing-digital/&quot;&gt;La personalización por segmentos&lt;/a&gt; —mostrar contenido diferente a «usuarios de Madrid mayores de 35 años»— es la versión obsoleta del problema. La hiperpersonalización dinámica de 2026 construye recorridos únicos por usuario en tiempo real: interfaces, mensajes y flujos que cambian según el comportamiento, la intención y el contexto en ese momento exacto.&lt;/p&gt;&lt;p&gt;El 60% de los usuarios vuelve a una plataforma si la experiencia estaba bien personalizada. El 88% no regresa si la experiencia fue mala. Son los dos extremos del mismo vector: la experiencia ya no se valora como un extra, se exige como mínimo.&lt;/p&gt;&lt;h2&gt;6. Zero UI: cuando la mejor interfaz es ninguna&lt;/h2&gt;&lt;p&gt;Zero UI no significa ausencia de diseño. Significa que el punto de contacto entre el usuario y el sistema no es una pantalla: es la voz, el gesto, el contexto. Para 2026, se estima que 157 millones de personas solo en Estados Unidos utilizarán asistentes de voz de forma regular. En España, la adopción crece especialmente en el segmento de 45 a 65 años, el perfil exacto de muchos decisores de compra B2B.&lt;/p&gt;&lt;p&gt;Las empresas que están ganando en este espacio no están diseñando «una funcionalidad de voz». Están rediseñando flujos enteros desde cero asumiendo que la voz es el canal primario, no un complemento.&lt;/p&gt;&lt;h2&gt;7. Sostenibilidad digital (Green UX): eficiencia que también convierte&lt;/h2&gt;&lt;p&gt;El Green UX parte de una premisa técnica —reducir el peso de las interfaces para consumir menos energía y ancho de banda— pero tiene consecuencias directas en rendimiento. Una interfaz más ligera carga más rápido. Un segundo de mejora en el tiempo de carga puede suponer entre un 7% y un 10% más de conversiones, según datos consolidados del sector.&lt;/p&gt;&lt;p&gt;En 2026, la sostenibilidad digital también impacta en percepción de marca, especialmente en sectores B2B donde los criterios ESG forman parte del proceso de evaluación de proveedores.&lt;/p&gt;&lt;h2&gt;8. Accesibilidad obligatoria: la Ley Europea ya tiene fecha&lt;/h2&gt;&lt;p&gt;La European Accessibility Act entró en vigor para productos y servicios nuevos en 2025 y se aplicará a todos los productos digitales antes de 2030. Los equipos de diseño que tratan la accesibilidad como un checklist de última hora están construyendo deuda técnica y legal al mismo tiempo.&lt;/p&gt;&lt;p&gt;En 2026, la accesibilidad digital va más allá de las discapacidades visuales clásicas. Incluye diseñar para neurodivergencias, distintos niveles de alfabetización digital y contextos de uso extremos —conducción, baja luminosidad, pantallas pequeñas. Un 23% de los usuarios europeos activos tiene algún tipo de condición que afecta a su interacción con interfaces digitales.&lt;/p&gt;&lt;h2&gt;9. Modo oscuro como estándar, no como opción&lt;/h2&gt;&lt;p&gt;El 82% de los usuarios tiene el modo oscuro activado por defecto en sus dispositivos. Las plataformas que lo tratan como una «preferencia alternativa» están degradando la experiencia de casi nueve de cada diez usuarios. Las interfaces diseñadas nativamente en dark mode —no como inversión de colores, sino con su propia jerarquía y paleta— han demostrado reducir la tasa de rebote hasta en un 60% y aumentar las páginas por sesión en un 170%.&lt;/p&gt;&lt;p&gt;La trampa habitual: asumir que si el sistema operativo gestiona el cambio de modo, el diseño ya está cubierto. No lo está. Las transiciones automáticas destruyen la jerarquía visual si no se ha diseñado la capa oscura desde el principio.&lt;/p&gt;&lt;h2&gt;10. Micro-interacciones y motion design: el lenguaje silencioso de la conversión&lt;/h2&gt;&lt;p&gt;El 50% de los diseñadores ya integra &lt;a href=&quot;https://inprofit.eu/tendencias-marketing/micro-momentos-en-marketing-como-capturar-a-tu-audiencia-en-segundos-con-hiper-personalizacion/&quot;&gt;micro-interacciones y animaciones&lt;/a&gt; en sus proyectos actuales. No como adorno: como comunicación funcional. Una animación bien ejecutada en un formulario de pago elimina la ansiedad del usuario y reduce el abandono. Un feedback visual en tiempo real durante el onboarding puede incrementar la tasa de finalización en más de un 30%.&lt;/p&gt;&lt;p&gt;El criterio para usar motion design en 2026 es sencillo: si la animación no le dice al usuario algo que el texto no puede decir mejor, sobra. Si lo dice mejor, es imprescindible.&lt;/p&gt;&lt;h2&gt;11. IA explicable: la transparencia como ventaja competitiva&lt;/h2&gt;&lt;p&gt;Los usuarios de 2026 saben que hay IA detrás de las recomendaciones, los precios dinámicos y los rankings de búsqueda. Lo que exigen es que el sistema sea capaz de explicarse. Los productos que muestran su razonamiento —»te recomendamos esto porque has consultado X»— generan más confianza y más retención que los que presentan resultados sin contexto.&lt;/p&gt;&lt;p&gt;El 54% de los responsables de producto reconocen que sus &lt;a href=&quot;https://inprofit.eu/inteligencia-artificial-alicante/&quot;&gt;empresas están implementando IA&lt;/a&gt; en la interfaz sin una lógica clara para el usuario. Esta brecha entre lo que el sistema hace y lo que el usuario entiende es hoy uno de los principales focos de fricción y abandono en productos digitales.&lt;/p&gt;&lt;h2&gt;12. Investigación continua: el fin del UX por proyecto&lt;/h2&gt;&lt;p&gt;El modelo tradicional —investigar al inicio del proyecto, entregar, olvidarse— está siendo reemplazado por sistemas de investigación continua donde el feedback del usuario se recoge, procesa e integra de forma permanente. No como una encuesta trimestral: como un sistema vivo que alimenta decisiones de producto cada semana.&lt;/p&gt;&lt;p&gt;Las empresas que operan con este modelo detectan problemas de experiencia antes de que se conviertan en problemas de retención. La diferencia entre corregir un flujo de checkout con tres semanas de datos y hacerlo con tres días de datos puede ser, en volúmenes medios, decenas de miles de euros en ingresos recuperados.&lt;/p&gt;&lt;h2&gt;La pregunta que tiene respuesta&lt;/h2&gt;&lt;p&gt;¿Cuántas de estas 12 tendencias UX UI 2026 están ya integradas en la hoja de ruta de producto de tu empresa? Si la respuesta es «ninguna» o «estamos evaluándolo», el problema no es de diseño. Es de velocidad de decisión. Las empresas que están ganando en experiencia digital no esperan a que la tendencia esté consolidada. Se posicionan cuando todavía hay ventana.&lt;/p&gt;&lt;p&gt;En Inprofit trabajamos con empresas que quieren &lt;a href=&quot;https://inprofit.eu/marketing-estudio/agencia-e-commerce/&quot;&gt;convertir su interfaz en una palanca de negocio real&lt;/a&gt;, no en un coste de mantenimiento. Cuéntanos qué está frenando tu experiencia digital.&lt;/p&gt;&lt;h3&gt;Preguntas frecuentes sobre tendencias UX/UI 2026&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;strong&gt;¿Qué es el Generative UI y cómo afecta a las empresas en 2026?&lt;/strong&gt;&lt;p&gt;Generative UI son sistemas de interfaz que reorganizan su estructura visual en tiempo real en función del contexto del usuario, sin intervención humana. No adaptan contenido: adaptan la arquitectura completa de la pantalla. Las empresas que lo implementan correctamente registran mejoras de conversión del 15-25%, según datos del sector de 2026.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;¿Qué multas aplica el DSA por dark patterns en interfaces digitales?&lt;/strong&gt;&lt;p&gt;El Reglamento de Servicios Digitales (DSA) de la UE, en su Artículo 25, prohíbe las interfaces manipuladoras con sanciones de hasta el 6% de los ingresos globales de la empresa. La aplicación efectiva de estas multas comenzó en el primer trimestre de 2026 y afecta a todas las plataformas con presencia en el mercado europeo.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;¿Cuál es la diferencia entre personalización e hiperpersonalización en UX?&lt;/strong&gt;&lt;p&gt;La personalización adapta contenido a segmentos de usuarios. La hiperpersonalización dinámica construye recorridos individuales en tiempo real para cada usuario, ajustando no solo qué se muestra, sino cómo se estructura la interfaz, el tono del mensaje y el orden de los pasos, en función del comportamiento en ese momento exacto.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;¿A qué empresas afecta la European Accessibility Act en 2026?&lt;/strong&gt;&lt;p&gt;Afecta a todas las empresas que ofrezcan productos o servicios digitales en la Unión Europea: webs, apps, software de autoservicio y plataformas de comercio electrónico. Entró en vigor para nuevos productos en 2025 y se aplicará a todos los productos digitales existentes antes de 2030.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;La entrada &lt;a href=&quot;https://inprofit.eu/web/tendencias-ux-ui-2026/&quot;&gt;12 tendencias UX/UI de 2026 que ya tienen coste para tu negocio&lt;/a&gt; se publicó primero en &lt;a href=&quot;https://inprofit.eu&quot;&gt;Inprofit&lt;/a&gt;.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>[Repost] CSS or BS</title>
<link>https://danq.me/2026/05/14/css-or-bs/</link>
<guid isPermaLink="false">BdVBEUeK-_8yEPNDUcyfX6pG6atQsDa1Gwtnkw==</guid>
<pubDate>Mon, 18 May 2026 05:21:29 +0000</pubDate>
<description>Keith Cirkel&#39;s made a game where you have to identify if something is a CSS property or a convincing fake. How&#39;s your CSS knowledge?</description>
<content:encoded>&lt;blockquote&gt;&lt;div&gt;&lt;p&gt;&lt;a href=&quot;https://www.keithcirkel.co.uk/css-or-bs/&quot;&gt;&lt;img src=&quot;https://danq.me/wp-content/uploads/2026/05/Screenshot-2026-05-14-at-16.20.36-980x594.png&quot; alt=&quot;CSS or BS game in progress. The player is asked to declare whether &amp;#39;view-timeline-name&amp;#39; is a real CSS property or made-up.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;footer&gt;&lt;cite&gt;&lt;a href=&quot;https://www.keithcirkel.co.uk/&quot;&gt;Keith Cirkel&lt;/a&gt;, in &lt;a href=&quot;https://www.keithcirkel.co.uk/css-or-bs/&quot;&gt;CSS or BS&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;&lt;/blockquote&gt;&lt;p&gt;Well this is a fun (and frustrating!) game. You’ll be presented with 20 (alleged) CSS properties, but some of them… are convincing-looking fakes! You’ve got 10 seconds to identify whether each is real or not. Every few you get right increases the difficulty level, but also the score potential. How high can you score?&lt;/p&gt;&lt;p&gt;Me? Oh, I kept getting up into the “forbidden” level and then my brain would melt and I’d crash out. Quite proud of my last run, though:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://danq.me/wp-content/uploads/2026/05/Screenshot-2026-05-14-at-16.48.04.png&quot;&gt;&lt;img src=&quot;https://danq.me/wp-content/uploads/2026/05/Screenshot-2026-05-14-at-16.48.04-640x326.png&quot; alt=&quot;Final score: 61/80. Reached: Forbidden. &amp;quot;If CSS knowledge were currency, you&amp;#39;d be comfortably middle-class.&amp;quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;👏 Congratulations on being an RSS user. 🎉&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Dynamically-Deployed Static Site Subdomains on Caddy – Dan Q</title>
<link>https://danq.me/2026/05/08/wildcard-caddy-with-github-webhooks/</link>
<enclosure type="image/jpeg" length="0" url="https://danq.me/_q23-og/29094/0.png"></enclosure>
<guid isPermaLink="false">lh5uwvn0FTd0-JLrNcKQC4yTkZUbtuVeOvnZDw==</guid>
<pubDate>Mon, 18 May 2026 05:21:29 +0000</pubDate>
<description>Continuing my effort to find a way to serve my various static sites that meets my need for ease-of-use and low-maintenance, I configured a wildcard Caddy server with webhook-based monitoring of a GitHub repository. Here&#39;s how it works...</description>
<content:encoded>&lt;p&gt;
              &lt;a href=&quot;https://danq.me/2026/05/04/github-to-codeberg-pages/&quot;&gt;I’ve recently been experimenting with where I host my small and open-source static sites&lt;/a&gt;. In my latest experiment, I wanted to try a
              low-maintenance selfhosting solution&lt;sup&gt;1&lt;/sup&gt;.
              Here’s what I wanted:
            &lt;/p&gt;&lt;ul&gt;
              &lt;li&gt;Pushing to the &lt;code&gt;main&lt;/code&gt; branch of my GitHub/Codeberg/wherever repo would send a webook to my server.
              &lt;/li&gt;
              &lt;li&gt;Upon receiving the webhook, my server would pull the latest changes&lt;sup&gt;2&lt;/sup&gt;.
              &lt;/li&gt;
              &lt;li&gt;Using a wildcard certificate, my webserver automatically mounts each project at a subdomain matching its project name&lt;sup&gt;3&lt;/sup&gt;.
              &lt;/li&gt;
            &lt;/ul&gt;&lt;p&gt;
              Here’s what I came up with:
            &lt;/p&gt;&lt;h2&gt;
              Step 1: webhook handler
            &lt;/h2&gt;&lt;p&gt;
              I’m using &lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; as my webserver, because despite its considerable power and versatility &lt;a href=&quot;https://danq.me/2026/01/21/php-caddy-debian-13/&quot;&gt;it’s a breeze
              to set up&lt;/a&gt;. To sort wildcard DNS later I’ll want to swap in a custom build, but to get started I just ran &lt;code&gt;apt install caddy&lt;/code&gt;. Then I used &lt;code&gt;apt install webhook&lt;/code&gt;
              to install Adnan Hajdarević’s &lt;a href=&quot;https://github.com/adnanh/webhook&quot;&gt;webhook&lt;/a&gt; endpoint, and tied the two together in my Caddyfile:
            &lt;/p&gt;&lt;p&gt;
              Then I created a webhook in a GitHub repository:
            &lt;/p&gt;&lt;figure&gt;
              &lt;img src=&quot;https://bcdn.danq.me/_q23u/2026/05/Screenshot-2026-05-08-at-13.17.23-640x345.jpg&quot; alt=&quot;GitHub webhook pointing to https://webhook.duckling.danq.me/hooks/github-push, with a secret set and SSL verification enabled, triggered by a push event.&quot; title=&quot;&quot;/&gt;
              &lt;figcaption&gt;
                I generated a long random string to use as the secret, and kept a copy for later.
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              When you create a webhook in GitHub it immediately sends a test event, but it doesn’t &lt;em&gt;quite&lt;/em&gt; look like a real push event so I pushed an inconsequential change to the repo
              to trigger another. Once you’ve got a “real” one sent, you can re-send it via the “Recent Deliveries” tab as many times as you like, to help with testing.
            &lt;/p&gt;&lt;p&gt;
              Then, on the server, I checked-out a copy of the code (anonymously: this is a public repository so I don’t need keys to read from it anyway) and set up my /etc/webhook.conf to expect
              these calls:
            &lt;/p&gt;&lt;figure&gt;
              &lt;div&gt;
                &lt;pre&gt;[
  &lt;span&gt;  &lt;/span&gt;{
  &lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;quot;id&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;github-push&amp;quot;&lt;/span&gt;,
  &lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;quot;execute-command&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;/var/www/github-push/webhook.sh&amp;quot;&lt;/span&gt;,
  &lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;quot;command-working-directory&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;/var/www/github-push/&amp;quot;&lt;/span&gt;,
  &lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;quot;pass-arguments-to-command&amp;quot;&lt;/span&gt;: [
  &lt;span&gt;      &lt;/span&gt;{
  &lt;span&gt;        &lt;/span&gt;&lt;span&gt;&amp;quot;source&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;payload&amp;quot;&lt;/span&gt;,
  &lt;span&gt;        &lt;/span&gt;&lt;span&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;repository.name&amp;quot;&lt;/span&gt;
  &lt;span&gt;      &lt;/span&gt;}
  &lt;span&gt;    &lt;/span&gt;],
  &lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;quot;trigger-rule&amp;quot;&lt;/span&gt;: {
  &lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;quot;and&amp;quot;&lt;/span&gt;: [
  &lt;span&gt;        &lt;/span&gt;{
  &lt;span&gt;          &lt;/span&gt;&lt;span&gt;&amp;quot;match&amp;quot;&lt;/span&gt;: {
  &lt;span&gt;            &lt;/span&gt;&lt;span&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;payload-hash-sha256&amp;quot;&lt;/span&gt;,
  &lt;span&gt;            &lt;/span&gt;&lt;span&gt;&amp;quot;secret&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;[MY SECRET KEY HERE]&amp;quot;&lt;/span&gt;,
  &lt;span&gt;            &lt;/span&gt;&lt;span&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;: {
  &lt;span&gt;              &lt;/span&gt;&lt;span&gt;&amp;quot;source&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;header&amp;quot;&lt;/span&gt;,
  &lt;span&gt;              &lt;/span&gt;&lt;span&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;X-Hub-Signature-256&amp;quot;&lt;/span&gt;
  &lt;span&gt;            &lt;/span&gt;}
  &lt;span&gt;          &lt;/span&gt;}
  &lt;span&gt;        &lt;/span&gt;},
  &lt;span&gt;        &lt;/span&gt;{
  &lt;span&gt;          &lt;/span&gt;&lt;span&gt;&amp;quot;match&amp;quot;&lt;/span&gt;: {
  &lt;span&gt;            &lt;/span&gt;&lt;span&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;value&amp;quot;&lt;/span&gt;,
  &lt;span&gt;            &lt;/span&gt;&lt;span&gt;&amp;quot;value&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;refs/heads/main&amp;quot;&lt;/span&gt;,
  &lt;span&gt;            &lt;/span&gt;&lt;span&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;: {
  &lt;span&gt;              &lt;/span&gt;&lt;span&gt;&amp;quot;source&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;payload&amp;quot;&lt;/span&gt;,
  &lt;span&gt;              &lt;/span&gt;&lt;span&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span&gt;&amp;quot;ref&amp;quot;&lt;/span&gt;
  &lt;span&gt;            &lt;/span&gt;}
  &lt;span&gt;          &lt;/span&gt;}
  &lt;span&gt;        &lt;/span&gt;}
  &lt;span&gt;      &lt;/span&gt;]
  &lt;span&gt;    &lt;/span&gt;}
  &lt;span&gt;  &lt;/span&gt;}
  ]
  &lt;/pre&gt;
              &lt;/div&gt;
              &lt;figcaption&gt;
                The &lt;code&gt;trigger-rule&lt;/code&gt; directives ensure that (a) the secret key is correct (it uses a HMAC hash across the entire JSON request, so it prevents payload tampering too) and
                (b) the event only triggers on pushes to the &lt;code&gt;main&lt;/code&gt; branch. The &lt;code&gt;execute-command&lt;/code&gt; specifies the Bash script I want to run when the webhook is triggered. The
                &lt;code&gt;pass-arguments-to-command&lt;/code&gt; configuration says to send the repo name on to that script.
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              Now all I needed to do was write the &lt;code&gt;/var/www/github-push/webhook.sh&lt;/code&gt; Bash script so that it pulled the latest copy of the code when triggered:
            &lt;/p&gt;&lt;p&gt;
              I was able to test this by pushing inconsequential changes to my codebase and watching them get replicated down to my webserver. Neat!
            &lt;/p&gt;&lt;h2&gt;
              Step 2: low-maintenance webserver
            &lt;/h2&gt;&lt;p&gt;
              After pointing the DNS for &lt;code&gt;*.static.duckling.danq.me&lt;/code&gt; at my static server, I set about configuring Caddy to be able to use DNS-01 challenges to get itself wildcard SSL
              certificates&lt;sup&gt;4&lt;/sup&gt;.
              Caddy can’t do DNS-01 challenges out of the box, so you either need to write your own renewal script or compile Caddy with plugins corresponding to your DNS provider. My domains’ DNS
              are managed by a mixture of AWS Route 53, Gandi, and Namecheap, so my &lt;a href=&quot;https://github.com/caddyserver/xcaddy&quot;&gt;xcaddy&lt;/a&gt; build step looked like this:
            &lt;/p&gt;&lt;p&gt;
              Of course, if I’d have preferred somebody else build it for me, &lt;a href=&quot;https://caddyserver.com/download?package=github.com%2Fcaddy-dns%2Froute53&amp;amp;package=github.com%2Fcaddy-dns%2Fgandi&amp;amp;package=github.com%2Fcaddy-dns%2Fnamecheap&quot;&gt;CaddyServer’s
              download configurator would have done it for me&lt;/a&gt; on-demand.
            &lt;/p&gt;&lt;p&gt;
              For Gandi and Namecheap I just need a personal access token or API key, respectively, but Route 53’s configuration is slightly more-involved: I needed to create a new user via IAM and
              give it permission to write DNS TXT records for the appropriate hosted zone. Fortunately &lt;a href=&quot;https://github.com/caddy-dns/route53#authenticating&quot;&gt;the guide for the
              caddy-dns/route53 repo had an almost copy-pastable example&lt;/a&gt;.
            &lt;/p&gt;&lt;p&gt;
              I added the AWS access key and secret key as environment variables (&lt;a href=&quot;https://www.baeldung.com/linux/systemd-services-environment-variables#setting-variables-with-environment&quot;&gt;like this!&lt;/a&gt;) into my
              &lt;code&gt;/etc/systemd/system/multi-user.target.wants/caddy.service service definition&lt;/code&gt;, and then told my Caddyfile to make use of them when renewing the wildcard certificate:
            &lt;/p&gt;&lt;figure&gt;
              &lt;div&gt;
                &lt;pre&gt;&lt;span&gt;*.static.duckling.danq.me&lt;/span&gt; {
  &lt;span&gt;  &lt;/span&gt;&lt;span&gt;tls&lt;/span&gt; {
    &lt;span&gt;    &lt;/span&gt;&lt;span&gt;dns&lt;/span&gt; &lt;span&gt;route53&lt;/span&gt; {
    &lt;span&gt;      &lt;/span&gt;&lt;span&gt;access_key_id&lt;/span&gt; &lt;span&gt;{&lt;span&gt;env.AWS_ACCESS_KEY_ID&lt;/span&gt;}&lt;/span&gt;
    &lt;span&gt;      &lt;/span&gt;&lt;span&gt;secret_access_key&lt;/span&gt; &lt;span&gt;{&lt;span&gt;env.AWS_SECRET_ACCESS_KEY&lt;/span&gt;}&lt;/span&gt;
    &lt;span&gt;    &lt;/span&gt;}
    &lt;span&gt;  &lt;/span&gt;}
    &lt;span&gt;  &lt;/span&gt;&lt;span&gt;root&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;/&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;www&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;github-push&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;{&lt;span&gt;http.request.host.labels.4&lt;/span&gt;}
    &lt;span&gt;  &lt;/span&gt;&lt;span&gt;file_server&lt;/span&gt;
    }
}&lt;/pre&gt;
              &lt;/div&gt;
              &lt;figcaption&gt;
                The &lt;code&gt;{http.request.host.labels.4}&lt;/code&gt; refers to the fourth part of the domain name, when separated at the dots and counted from the right, so &lt;code&gt;0 = me&lt;/code&gt;, &lt;code&gt;1 =
                danq&lt;/code&gt;, &lt;code&gt;2 = duckling&lt;/code&gt;, &lt;code&gt;3 = static&lt;/code&gt;, and &lt;code&gt;4&lt;/code&gt; = the part that we’re interested in. So long as I don’t store any other directories in the
                &lt;code&gt;/var/www/github-push/&lt;/code&gt; directory then this will simply map each subdomain onto its git repository name and return a 404 for any other request.
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              &lt;code&gt;DNS-01&lt;/code&gt; challenges are necessarily slower than &lt;code&gt;HTTP-01&lt;/code&gt;/&lt;code&gt;ALPN&lt;/code&gt; challenges, because they’re limited by DNS propogation, so it took a while before the
              certificate was issued. I ran Caddy in the foreground to watch the logs while it did so:
            &lt;/p&gt;&lt;p&gt;
              &lt;img src=&quot;https://bcdn.danq.me/_q23u/2026/05/Screenshot-2026-05-08-at-14.48.41-640x234.jpg&quot; alt=&quot;Caddy webserver logs, with a highlighted section showing a DNS-01 challenge for *.static.duckling.danq.me repeatedly fail and then eventually succeed, then a certificate chain being installed.&quot; title=&quot;&quot;/&gt;
            &lt;/p&gt;&lt;p&gt;
              You can see the whole thing working (for now at least; I don’t know if I’m keeping this approach!) by going to e.g. &lt;a href=&quot;https://embed-html.static.duckling.danq.me/&quot;&gt;embed-html.static.duckling.danq.me&lt;/a&gt;, which dynamically tracks the &lt;code&gt;main&lt;/code&gt; branch of &lt;a href=&quot;https://github.com/dan-q/embed-html&quot;&gt;the embed-html repo on GitHub&lt;/a&gt;.
            &lt;/p&gt;&lt;p&gt;
              I don’t yet know if this is going to be the future forever-home of my many static site side projects, but it’s certainly been the most-satisfying experiment to run so-far.
            &lt;/p&gt;&lt;div&gt;
              &lt;h2&gt;
                Footnotes
              &lt;/h2&gt;
              &lt;p&gt;
                &lt;sup&gt;1&lt;/sup&gt; I’ve drifted away from selfhosting simple static sites lately because I’ve accidentally
                broken them with configuration changes too many times! But I figured I’d be open to in-housing them again if I had a single simple architecture for them all, so I spun up a VPS and
                gave it a go
              &lt;/p&gt;
              &lt;p&gt;
                &lt;sup&gt;2&lt;/sup&gt; Running a build script or some other static site generation tool is out of scope for
                now, but I want to be able to confirm that it would be possible in the future.
              &lt;/p&gt;
              &lt;p&gt;
                &lt;sup&gt;3&lt;/sup&gt; It also needs to be possible for me to map &lt;em&gt;other&lt;/em&gt; domain names to it, but that’s
                a triviality.
              &lt;/p&gt;
              &lt;p&gt;
                &lt;sup&gt;4&lt;/sup&gt; It’s &lt;a href=&quot;https://caddyserver.com/docs/caddyfile/options#on-demand-tls&quot;&gt;absolutely
                possible&lt;/a&gt; to use &lt;code&gt;tls { on_demand }&lt;/code&gt; to do this, but it’s &lt;em&gt;better&lt;/em&gt; to use a wildcard certificate which can be pre-generated and doesn’t let people trick your
                server into making ludicrous numbers of certificate requests by hammering random subdomain names.
              &lt;/p&gt;
            &lt;/div&gt;&lt;div&gt;
               &lt;data&gt; &lt;data&gt; &lt;data&gt; &lt;data&gt;
            &lt;/data&gt;&lt;/data&gt;&lt;/data&gt;&lt;/data&gt;&lt;/div&gt;</content:encoded>
</item>
<item>
<title>Bitsocial - Open Source P2P Network for Social Apps</title>
<link>https://bitsocial.net/</link>
<enclosure type="image/jpeg" length="0" url="https://bitsocial.net/hero-fallback-desktop-light.png"></enclosure>
<guid isPermaLink="false">f0ygTNKl3ETYfGFsCAvbRXx_ZQnWaPYKxb3Hkw==</guid>
<pubDate>Sun, 17 May 2026 22:51:25 +0000</pubDate>
<description>Bitsocial is an open-source IPFS-backed peer-to-peer network for social apps, with no servers, no global bans, where users and communities are cryptographic property.</description>
<content:encoded>&lt;section&gt;&lt;div&gt;&lt;h2&gt;Core Features&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Bitsocial is &lt;strong&gt;free and open source&lt;/strong&gt; software: the protocol and clients are public, forkable, and open to outside contributions. &lt;strong&gt;Anyone can inspect the code&lt;/strong&gt;, ship improvements, or build a compatible app without asking permission from a company.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/bitsocialnet&quot;&gt;Browse source code&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/bitsocialnet&quot;&gt;Browse source code&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Bitsocial is &lt;strong&gt;neither federated nor on-chain&lt;/strong&gt;. Each community is a peer-to-peer swarm using &lt;a href=&quot;https://ipfs.tech/&quot;&gt;IPFS&lt;/a&gt;, &lt;strong&gt;closer to BitTorrent&lt;/strong&gt; than a hosted website: users seed effortlessly, nodes run on cheap hardware, and each community is fully independent and sovereign.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/peer-to-peer-protocol/&quot;&gt;Learn how p2p works&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/peer-to-peer-protocol/&quot;&gt;Learn how p2p works&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Anyone can build a Bitsocial app with its own interface, discovery model, or defaults. Apps compete on product quality instead of locking users into a private database, because compatible clients can &lt;strong&gt;share the same communities, identities, and network&lt;/strong&gt;.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/apps&quot;&gt;Explore apps&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/apps&quot;&gt;Explore apps&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Bitsocial does not require every community to rent a datacenter box, buy a domain, or manage SSL just to stay online. A community node can run from home on consumer hardware, and &lt;strong&gt;there is no single company-run backend that can take the whole network down&lt;/strong&gt;.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/stats/&quot;&gt;Check network stats&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/stats/&quot;&gt;Check network stats&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Moderation still exists, but it stays local. Community owners set rules for their own spaces and apps can choose what they index or show, yet &lt;strong&gt;there is no protocol-level super-admin&lt;/strong&gt; who can erase a profile or seize a community from the network itself.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/local-moderation/&quot;&gt;Read moderation notes&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/local-moderation/&quot;&gt;Read moderation notes&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Profiles and communities are controlled by private keys, not by revocable platform accounts. You can delegate hosting without giving away ownership, so &lt;strong&gt;your identity and community behave more like wallet-controlled property&lt;/strong&gt; than a username rented from a company.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/identity-and-ownership/&quot;&gt;Read ownership notes&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/identity-and-ownership/&quot;&gt;Read ownership notes&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Sanctuary Communication&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;It just works.&lt;/h2&gt;&lt;p&gt;No servers to rent. No domains to buy. No chains to sync. A Bitsocial node runs on a Raspberry Pi. And each community can enforce its own anti-spam challenge: captchas, reputation, SMS, payments, tokens, IP checks, or anything else that can be coded.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;Federated&lt;/h3&gt;&lt;p&gt;Bluesky, Mastodon, Lemmy&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Self-hosting cost&lt;/p&gt;&lt;div&gt;&lt;p&gt;Server + domain + SSL&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Who keeps it online&lt;/p&gt;&lt;div&gt;&lt;p&gt;Service / instance operator&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Scaling model&lt;/p&gt;&lt;div&gt;&lt;p&gt;Bigger instances, higher bills&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Custom anti-spam logic&lt;/p&gt;&lt;div&gt;&lt;p&gt;Not built into the protocol&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Takedown choke points&lt;/p&gt;&lt;div&gt;&lt;p&gt;Host, registrar, SSL, DDoS provider&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;Chain / Hub&lt;/h3&gt;&lt;p&gt;Farcaster, Lens, DeSo, Steemit&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Self-hosting cost&lt;/p&gt;&lt;div&gt;&lt;p&gt;Expensive node, hub, or RPC&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Who keeps it online&lt;/p&gt;&lt;div&gt;&lt;p&gt;Chain, hub, or RPC infrastructure&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Scaling model&lt;/p&gt;&lt;div&gt;&lt;p&gt;More state, heavier infra&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Custom anti-spam logic&lt;/p&gt;&lt;div&gt;&lt;p&gt;Tied to chains, hubs, or fees&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Takedown choke points&lt;/p&gt;&lt;div&gt;&lt;p&gt;Validators, hubs, RPCs&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;Bitsocial&lt;/h3&gt;&lt;p&gt;Pure P2P with arbitrary anti-spam challenges&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Self-hosting cost&lt;/p&gt;&lt;div&gt;&lt;p&gt;Extremely cheap, runs on Raspberry Pi&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Who keeps it online&lt;/p&gt;&lt;div&gt;&lt;p&gt;Community owners + helper seeders&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Scaling model&lt;/p&gt;&lt;div&gt;&lt;p&gt;More peers, more bandwidth&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Custom anti-spam logic&lt;/p&gt;&lt;div&gt;&lt;p&gt;Built in: challenge can be anything&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Takedown choke points&lt;/p&gt;&lt;div&gt;&lt;p&gt;No single choke point&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;Federated&lt;/h3&gt;&lt;p&gt;Bluesky, Mastodon, Lemmy&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Self-hosting cost&lt;/p&gt;&lt;div&gt;&lt;p&gt;Server + domain + SSL&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Who keeps it online&lt;/p&gt;&lt;div&gt;&lt;p&gt;Service / instance operator&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Scaling model&lt;/p&gt;&lt;div&gt;&lt;p&gt;Bigger instances, higher bills&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Custom anti-spam logic&lt;/p&gt;&lt;div&gt;&lt;p&gt;Not built into the protocol&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Takedown choke points&lt;/p&gt;&lt;div&gt;&lt;p&gt;Host, registrar, SSL, DDoS provider&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;Chain / Hub&lt;/h3&gt;&lt;p&gt;Farcaster, Lens, DeSo, Steemit&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Self-hosting cost&lt;/p&gt;&lt;div&gt;&lt;p&gt;Expensive node, hub, or RPC&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Who keeps it online&lt;/p&gt;&lt;div&gt;&lt;p&gt;Chain, hub, or RPC infrastructure&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Scaling model&lt;/p&gt;&lt;div&gt;&lt;p&gt;More state, heavier infra&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Custom anti-spam logic&lt;/p&gt;&lt;div&gt;&lt;p&gt;Tied to chains, hubs, or fees&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Takedown choke points&lt;/p&gt;&lt;div&gt;&lt;p&gt;Validators, hubs, RPCs&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;Bitsocial&lt;/h3&gt;&lt;p&gt;Pure P2P with arbitrary anti-spam challenges&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Self-hosting cost&lt;/p&gt;&lt;div&gt;&lt;p&gt;Extremely cheap, runs on Raspberry Pi&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Who keeps it online&lt;/p&gt;&lt;div&gt;&lt;p&gt;Community owners + helper seeders&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Scaling model&lt;/p&gt;&lt;div&gt;&lt;p&gt;More peers, more bandwidth&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Custom anti-spam logic&lt;/p&gt;&lt;div&gt;&lt;p&gt;Built in: challenge can be anything&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Takedown choke points&lt;/p&gt;&lt;div&gt;&lt;p&gt;No single choke point&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;See deep comparison with:&lt;blockquote&gt;“Your community literally cannot be banned or blocked by anyone, even a government. We solve that problem.”&lt;span&gt;— Esteban Abaroa, Bitsocial founder&lt;/span&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Arbitrary Challenges&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;Anti-spam? Future-proof.&lt;/h2&gt;&lt;p&gt;Each node runs its own anti-spam challenge, exchanged purely peer-to-peer with the user. When spam evolves, nodes adapt without changing the protocol.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;User&lt;/p&gt;&lt;p&gt;publishes a post&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;Challenge&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;Challenge&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Bitsocial node&lt;/p&gt;&lt;p&gt;runs the challenge&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Challenge module&lt;/p&gt;&lt;p&gt;any code-backed test&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Plug in any challenge&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;any code&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;AI-powered moderation&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;SMS OTP&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;whatever comes next&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;blockquote&gt;“The only Bitsocial innovation is solving spam and DDoS using arbitrary challenges like captchas over p2p.”&lt;span&gt;— Esteban Abaroa, Bitsocial founder&lt;/span&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Master Plan&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;A Call to Action&lt;/h2&gt;&lt;p&gt;Bitsocial wins by attracting as many builders as possible. Run your own unstoppable community, develop your own decentralized social app, or even launch your own business on top of Bitsocial. The tide rises with every builder.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;01&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Phase 1&lt;span&gt; — Ongoing&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;Decentralize imageboards and forums&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Imageboards are the simplest form of social media to decentralize&lt;/strong&gt;: anonymous posting, few default boards, and no profile graph. 5chan proves Bitsocial can replace centralized imageboards while removing global admins from the equation.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://bitsocialforge.com&quot;&gt;Bitsocial Forge&lt;/a&gt; will launch the first non-custodial RPC service for Bitsocial apps. &lt;a href=&quot;https://bitsocial.net/docs/permissionless-public-rpc/&quot;&gt;Bitsocial RPC&lt;/a&gt; will let users manage nodes remotely, while preserving the option to self-host or run competing RPC infrastructure. &lt;strong&gt;Users will be able to create and manage unstoppable p2p communities from mobile&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Forums add persistent identities, post history, and community management. Seedit is the first prototype Bitsocial app for &lt;strong&gt;Reddit-style discussion&lt;/strong&gt;, and public RPC makes those always-on P2P communities practical from anywhere.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://5chan.app&quot;&gt;Try 5chan&lt;/a&gt;&lt;a href=&quot;https://seedit.app&quot;&gt;Try Seedit&lt;/a&gt;&lt;a href=&quot;https://bitsocial.net/docs/build-your-own-client/&quot;&gt;Build your own&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;02&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Phase 2&lt;/div&gt;&lt;h3&gt;Launch Bitsocial Network&lt;/h3&gt;&lt;p&gt;In order to decentralize all social media, Bitsocial apps will need killer features and strong network effects: &lt;strong&gt;unstoppable financial structures&lt;/strong&gt;, decentralized Bitsocial domains (.bso), awards and tipping, common liquidity, and practical monetization. All of this will be powered by Bitsocial Network, a &lt;strong&gt;decentralized appchain solution&lt;/strong&gt; for Bitsocial apps.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/bitsocial-network/&quot;&gt;Learn More&lt;span&gt;: Launch Bitsocial Network&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;03&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Phase 3&lt;/div&gt;&lt;h3&gt;Launch the flagship Bitsocial app&lt;/h3&gt;&lt;p&gt;The flagship Bitsocial app should be the first profile-based client: posts, replies, follows, real-time public conversation, and communities that can pull network effects from every Bitsocial client. &lt;strong&gt;By default, it can feel as familiar as a modern For You social app&lt;/strong&gt;, while still letting users switch RPCs, feeds, instances, algorithms, ads, or remove ranking entirely.&lt;/p&gt;&lt;p&gt;Through non-custodial RPC, each user can still act as a full p2p node without trusting the RPC with ownership. Anyone should be able to move to their own profile node on a low-spec machine, including a Raspberry Pi, whenever they want.&lt;/p&gt;&lt;p&gt;Because Phase 2 gives the app an unstoppable crypto financial layer, the flagship client can potentially become an &lt;strong&gt;everything-app&lt;/strong&gt; without turning into a custodial platform.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/flagship-bitsocial-app/&quot;&gt;Learn More&lt;span&gt;: Launch the flagship Bitsocial app&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;04&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Phase 4&lt;/div&gt;&lt;h3&gt;Scale Bitsocial economies&lt;/h3&gt;&lt;p&gt;The next layer is ecosystem funding and infrastructure pluralism. &lt;strong&gt;Bitsocial Forge&amp;#39;s RPC should not be the only successful RPC&lt;/strong&gt;: many businesses, independent teams, anonymous operators, and community entities should build RPCs, media hosting, discovery, moderation, and other services on top of Bitsocial. Funding should push that decentralization forward, so developers and operators can compete to make the network faster, cheaper, more reliable, and harder to capture.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/scale-bitsocial-economies/&quot;&gt;Learn More&lt;span&gt;: Scale Bitsocial economies&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;05&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Phase 5&lt;/div&gt;&lt;h3&gt;Decentralize all social media&lt;/h3&gt;&lt;p&gt;With the core apps, public RPCs, profile nodes, discovery, and monetization in place, &lt;strong&gt;Bitsocial can fund and build the long tail of social clients&lt;/strong&gt;: blogging, crowdfunding, creator video and a credible YouTube alternative, niche experiments, and every format too early for the first four phases. Bitsocial Forge can build some of them, centralized clients can compete too, they do not all have to be FOSS, and decentralized community grants can fund as many developers as possible.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://bitsocial.net/docs/decentralize-all-social-media/&quot;&gt;Learn More&lt;span&gt;: Decentralize all social media&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;The end state is not one app. It is a market of clients, nodes, services, and communities that can replace platform ownership with protocol competition.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Social media finally finds its equilibrium: a fully decentralized, peer-to-peer social network that nobody owns; Bitsocial.&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;div&gt;&lt;div&gt;&lt;h2&gt;Stay in the Loop&lt;/h2&gt;&lt;p&gt;Get notified about protocol milestones, new Bitsocial apps, and ecosystem updates. Low frequency, high signal.&lt;/p&gt;&lt;p&gt;No spam, ever. Unsubscribe anytime.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;</content:encoded>
</item>
<item>
<title>Why Are My Emails Going to Spam? Reasons and How to Fix Them</title>
<link>https://fluentsupport.com/why-are-my-emails-going-to-spam/</link>
<guid isPermaLink="false">PVCPAZsxUI_cHaL2tDpbNGzbZEWb1ayAlEd6Tg==</guid>
<pubDate>Sat, 16 May 2026 11:17:45 +0000</pubDate>
<description>Emails going to spam despite clean content and authentication? Here are 10 real reasons it happens and exactly how to fix each one. The post Why Are My Emails Going to Spam? Reasons and How to Fix Them appeared first on Fluent Support.</description>
<content:encoded>&lt;p&gt;You set up SPF, DKIM, and DMARC. Your subject line is clean. Your content is not salesy at all. And still, your emails are going to spam. It is one of the most disorienting deliverability problems because nothing looks obviously wrong, yet something clearly is.&lt;/p&gt;&lt;p&gt;The truth is that spam filters today are far more layered than most senders realize. They evaluate your domain reputation, engagement signals, sending patterns, list quality, and content structure all at once. Missing the mark on any one of them can quietly push your emails into the spam folder, even when everything else looks fine.&lt;/p&gt;&lt;p&gt;In this blog, we will walk through every real reason your emails might be landing in spam, how each fix works step by step, and what you need to maintain over time to stay in the inbox.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;TL;DR&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Authentication is necessary but not sufficient on its own&lt;/li&gt;&lt;li&gt;Sender reputation, list quality, and engagement rates are equally critical&lt;/li&gt;&lt;li&gt;Content structure, HTML quality, and sending volume patterns all affect spam scoring&lt;/li&gt;&lt;li&gt;Gmail and Yahoo now enforce one-click unsubscribe and stricter spam rate thresholds&lt;/li&gt;&lt;li&gt;Most deliverability problems have a root cause that compounds over time — fix the foundation first&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;What spam filters are actually evaluating&lt;/h2&gt;&lt;p&gt;Spam filters are not simple keyword detectors. Gmail, Outlook, Yahoo, and other providers use multi-signal algorithms that assign a score to each incoming email. If that score crosses a set threshold, the email goes to spam regardless of your intent.&lt;/p&gt;&lt;p&gt;According to &lt;a href=&quot;https://www.statista.com/statistics/420400/spam-email-traffic-share-annual/&quot;&gt;Statista&lt;/a&gt;, “nearly 45.6% of all emails worldwide were identified as spam in 2023.” That number climbed above 46.8% by December 2024. Spam filters have tightened dramatically in response to this volume, and legitimate senders now regularly get caught in rules designed for bad actors.&lt;/p&gt;&lt;p&gt;The signals these filters evaluate include your sending domain reputation, IP address history, whether your authentication records are properly configured, how recipients engage with your emails, whether you have clean list hygiene, and whether your content or formatting resembles known spam patterns. All of it runs together. That is why a technically clean email can still land in spam if your domain reputation is weak or your engagement rates have dropped.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/1.the-multi-layer-spam-scoring-model-fluent-support-blog.webp&quot; alt=&quot;the multi-layer spam scoring model, fluent support blog&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h2&gt;Why are my emails going to spam?&lt;/h2&gt;&lt;p&gt;The reasons fall into distinct categories. Understanding which one applies to you is the first step toward fixing it.&lt;/p&gt;&lt;h3&gt;1. Authentication Is missing or misconfigured&lt;/h3&gt;&lt;p&gt;Email authentication is the technical baseline every sender needs before anything else matters. Three protocols work together to establish your legitimacy with receiving mail servers.&lt;/p&gt;&lt;p&gt;SPF (Sender Policy Framework) defines which IP addresses are authorized to send mail on behalf of your domain. DKIM (DomainKeys Identified Mail) adds a cryptographic signature to your outgoing emails that proves they were not altered in transit. &lt;/p&gt;&lt;p&gt;DMARC (Domain-based Message Authentication, Reporting, and Conformance) ties the two together and tells receiving servers what to do when either check fails — reject the message, quarantine it, or let it pass.&lt;/p&gt;&lt;p&gt;When any of these records are missing, incorrectly formatted, or in conflict with each other, spam filters flag the email almost immediately. Missing DMARC alone is enough to cause consistent spam folder placement, even when your content is perfectly fine.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Log into your domain registrar or DNS provider and check whether SPF, DKIM, and DMARC TXT records exist for your sending domain.&lt;/li&gt;&lt;li&gt;Step 2: Use a free tool like &lt;a href=&quot;https://postmaster.google.com/&quot;&gt;Google Postmaster Tools&lt;/a&gt; or MXToolBox to verify that each record is correctly configured and passing checks.&lt;/li&gt;&lt;li&gt;Step 3: If DMARC is missing, add a basic record starting with a p=none policy, which monitors without blocking, then move to p=quarantine and eventually p=reject once you have confirmed all legitimate mail streams are authenticated.&lt;/li&gt;&lt;li&gt;Step 4: After any changes to your DNS records, re-test using MXToolBox to confirm the records propagated correctly.&lt;/li&gt;&lt;li&gt;Step 5: Repeat this verification process whenever you change email service providers or add a new sending tool.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;As &lt;a href=&quot;https://blog.google/products-and-platforms/products/gmail/gmail-security-authentication-spam-protection/&quot;&gt;Google’s Neil Kumaran stated&lt;/a&gt; when announcing Gmail’s new 2024 requirements, “Starting in 2024, we’ll require bulk senders to authenticate their emails, allow for easy unsubscription and stay under a reported spam threshold.” Authentication is no longer optional at any sending volume.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/2.-DNS-Records-Panel-Email-Authentication-Examples.webp&quot; alt=&quot;DNS Records Panel, Email Authentication Examples, Why Are My Emails Going to Spam, fluent support blog&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h3&gt;2. Weak sender reputation&lt;/h3&gt;&lt;p&gt;Your domain and sending IP both carry a reputation score that mail providers track over time. This score is built from your cumulative sending history, including bounce rates, spam complaint rates, engagement levels, and whether your sending volume is consistent.&lt;/p&gt;&lt;p&gt;A brand-new domain starts with no reputation at all, which is why new senders often struggle with deliverability even when doing everything correctly. A domain that has previously sent to poor-quality lists, generated complaints, or been associated with spam activity can develop a damaged reputation that takes months to repair.&lt;/p&gt;&lt;p&gt;If you are using a shared IP address through your email service provider, you also inherit some of the reputation of other senders on that same IP. This is one reason why reputable ESP infrastructure matters.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Set up Google Postmaster Tools for your sending domain to monitor your Gmail-specific domain reputation score.&lt;/li&gt;&lt;li&gt;Step 2: Check your domain and IP against public blocklists using MXToolBox or MultiRBL to see whether you appear on any known spam databases.&lt;/li&gt;&lt;li&gt;Step 3: If your reputation is poor, stop all large-volume sends immediately and focus on sending small batches only to your most engaged subscribers.&lt;/li&gt;&lt;li&gt;Step 4: Gradually increase volume as your engagement metrics improve, which signals to mail providers that your emails are wanted.&lt;/li&gt;&lt;li&gt;Step 5: Maintain this gradual, consistent sending pattern going forward, since sudden spikes after quiet periods look like compromised account behavior.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;3. High bounce rates&lt;/h3&gt;&lt;p&gt;Every hard bounce, which occurs when an email address does not exist, sends a negative signal to mail providers. A pattern of high hard bounces tells spam filters that you are not maintaining your list, and that behavior is strongly associated with purchased lists and scraping tactics.&lt;/p&gt;&lt;p&gt;Even a bounce rate above 2% starts to damage your sender reputation meaningfully. Most professional email programs aim to keep hard bounces below 0.5%.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Run your existing list through an email verification tool before your next send to identify invalid addresses.&lt;/li&gt;&lt;li&gt;Step 2: Remove every hard bounce immediately after it occurs. Do not send to a hard-bounced address again.&lt;/li&gt;&lt;li&gt;Step 3: Implement double opt-in for all new sign-ups so that unverified or mistyped addresses never make it onto your active list.&lt;/li&gt;&lt;li&gt;Step 4: Monitor your bounce rate report after every campaign and investigate any sudden increases, since a spike often indicates a form abuse problem or a data import issue.&lt;/li&gt;&lt;li&gt;Step 5: Review your &lt;a href=&quot;https://fluentsupport.com/email-management-best-practices-at-work/&quot;&gt;email mana&lt;/a&gt;&lt;a href=&quot;https://fluentsupport.com/email-management-best-practices-at-work/&quot;&gt;g&lt;/a&gt;&lt;a href=&quot;https://fluentsupport.com/email-management-best-practices-at-work/&quot;&gt;ement practices&lt;/a&gt; across your team to ensure list hygiene is treated as an ongoing process rather than a one-time cleanup.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;4. Low recipient engagement&lt;/h3&gt;&lt;p&gt;This is the reason most senders overlook, and it is increasingly the deciding factor for Gmail placement. Mail providers track how recipients interact with your emails. When a large portion of your list consistently ignores your emails, never opens them, never clicks anything, and never replies, those engagement signals tell spam filters that your emails are not wanted.&lt;/p&gt;&lt;p&gt;Over time, consistent low engagement pushes your emails further into spam or the promotions tab. The filter is not judging your content in isolation. It is comparing your emails to what that specific recipient typically engages with, which means personalization and relevance matter far more than generic broadcast messaging.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Segment your list and identify subscribers who have not opened or clicked any email in the past 90 days.&lt;/li&gt;&lt;li&gt;Step 2: Send a re-engagement campaign to that segment. Ask them directly whether they still want to receive your emails.&lt;/li&gt;&lt;li&gt;Step 3: Remove anyone who does not respond to the re-engagement campaign within a reasonable window. A smaller, engaged list delivers better inbox placement than a large, disengaged one every time.&lt;/li&gt;&lt;li&gt;Step 4: Improve the relevance of your content by sending based on subscriber interest, behavior, or purchase history rather than blasting the entire list with every send.&lt;/li&gt;&lt;li&gt;Step 5: Monitor your open rates, click rates, and spam complaint rates after every send using your email platform’s analytics.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;5. Spam trigger words and content issues&lt;/h3&gt;&lt;p&gt;Content is still a factor, though modern spam filters weigh it less heavily than reputation signals. Certain patterns in your subject line, preview text, or email body can push your spam score up enough to cause placement issues, especially with smaller mail servers and corporate inboxes that still rely heavily on content filtering.&lt;/p&gt;&lt;p&gt;Subject lines with all-caps words, multiple exclamation marks, or aggressive sales phrases like “ACT NOW,” “LIMITED TIME,” or “100% FREE” are among the fastest ways to trigger a filter. The same applies to misleading preview text, excessive links, and emails that are almost entirely images with very little actual text.&lt;/p&gt;&lt;p&gt;Broken or messy HTML is another content-layer trigger. Sloppy code is a known pattern in spam because bad actors do not take the time to clean their markup. Even legitimate senders using poorly formatted templates can get flagged as a result.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Review your subject line and preview text for aggressive promotional language. Write copy that is specific and honest rather than urgency-driven.&lt;/li&gt;&lt;li&gt;Step 2: Aim for a text-to-image ratio of roughly 60% text and 40% images. Do not send emails that are entirely image-based.&lt;/li&gt;&lt;li&gt;Step 3: Add descriptive alt text to every image in your email.&lt;/li&gt;&lt;li&gt;Step 4: Remove URL shorteners like bit.ly from all email links. Use your own domain for tracking links instead.&lt;/li&gt;&lt;li&gt;Step 5: Use a tested email template with clean HTML. Run your email through an HTML validator before sending and check rendering across multiple clients if possible.&lt;/li&gt;&lt;li&gt;Step 6: Avoid attaching files to marketing emails. If you need recipients to download something, host it on your website and link to it.&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/3.-Spam-flagged-email-vs-clean-deliverable-email.webp&quot; alt=&quot;Spam flagged email vs clean deliverable email, why Are My Emails Going to Spam, fluent support blog&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h3&gt;6. No unsubscribe option&lt;/h3&gt;&lt;p&gt;A missing or broken unsubscribe link is both a legal violation and a direct deliverability risk. When recipients cannot easily opt out, they hit the spam button instead. Each spam report damages your sender reputation and increases the likelihood that future emails land in spam for everyone on your list.&lt;/p&gt;&lt;p&gt;The CAN-SPAM Act requires a clear, working unsubscribe mechanism in every commercial email. GDPR adds further requirements for EU recipients, including documented consent and prompt removal.&lt;/p&gt;&lt;p&gt;Beyond compliance, &lt;a href=&quot;https://blog.google/products-and-platforms/products/gmail/gmail-security-authentication-spam-protection/&quot;&gt;Google’s official 2024 sender requirements&lt;/a&gt; now mandate one-click unsubscribe for all bulk senders sending to Gmail. Unsubscribe requests must be processed within two days. Yahoo has implemented equivalent requirements.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Add a visible, clearly worded unsubscribe link to the footer of every commercial email you send.&lt;/li&gt;&lt;li&gt;Step 2: Implement the List-Unsubscribe header in your email’s technical setup so that Gmail and other providers can surface the unsubscribe option directly in their interface.&lt;/li&gt;&lt;li&gt;Step 3: Process unsubscribe requests within 48 hours. Do not continue sending to any address after they have requested removal.&lt;/li&gt;&lt;li&gt;Step 4: Audit your templates to confirm the unsubscribe link actually works. Broken links are treated the same as a missing link by spam filters.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;7. Sudden volume spikes&lt;/h3&gt;&lt;p&gt;Sending a few hundred emails one week and tens of thousands the next is a classic spam signal. Mail providers expect gradual, consistent growth in sending volume. A sudden spike suggests either a compromised account or mass spam activity, and filters respond accordingly.&lt;/p&gt;&lt;p&gt;This matters most when you are launching a new campaign to a previously unused segment, switching to a new email service provider, or sending to a purchased or imported list for the first time.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Plan any volume increase gradually. A safe approach is to increase your daily sending volume by no more than 20% to 30% per week.&lt;/li&gt;&lt;li&gt;Step 2: If you are migrating to a new sending infrastructure, warm up the new domain or IP by sending first to your most engaged subscribers in small batches.&lt;/li&gt;&lt;li&gt;Step 3: Monitor your spam complaint rate and bounce rate closely during any volume ramp-up.&lt;/li&gt;&lt;li&gt;Step 4: Schedule your campaigns in advance so that sending volume is spread across days rather than concentrated in a single blast.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;8. Unprotected sign-up forms&lt;/h3&gt;&lt;p&gt;Form abuse is an underappreciated but real cause of spam problems. Bots and automated scripts will fill out unprotected forms with fake, invalid, or recycled email addresses. &lt;/p&gt;&lt;p&gt;Those addresses then generate hard bounces or trigger spam traps on your first send, which damages your reputation quickly.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Add CAPTCHA verification or honeypot fields to all sign-up forms to prevent bot submissions.&lt;/li&gt;&lt;li&gt;Step 2: Implement rate limiting on form submissions to block repeated submissions from the same IP address in a short window.&lt;/li&gt;&lt;li&gt;Step 3: Enable double opt-in for all forms. This ensures that only real people with access to the submitted email address end up on your active list.&lt;/li&gt;&lt;li&gt;Step 4: Monitor your list for unusual sign-up spikes, which can indicate an active bot attack on your forms.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;9. Sending from a free domain&lt;/h3&gt;&lt;p&gt;Using a Gmail, Yahoo, or Hotmail address to send business or marketing emails is a significant deliverability risk. Free domains cannot be properly authenticated for bulk sending the same way a custom domain can, and receiving mail servers treat them with suspicion at any meaningful volume.&lt;/p&gt;&lt;p&gt;Google and Yahoo’s &lt;a href=&quot;https://support.google.com/a/answer/81126&quot;&gt;2024 sender requirements&lt;/a&gt; explicitly address this. Bulk sending from a free domain is likely to result in rejection or consistent spam folder placement.&lt;/p&gt;&lt;h4&gt;How to fix it, step by step:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Step 1: Register a custom domain for your business if you do not already have one.&lt;/li&gt;&lt;li&gt;Step 2: Set up a professional email address on that domain as your primary sending address.&lt;/li&gt;&lt;li&gt;Step 3: Configure SPF, DKIM, and DMARC records for the custom domain before sending any campaigns.&lt;/li&gt;&lt;li&gt;Step 4: Make sure the “From” name and address are consistent across all your emails so recipients recognize who is sending.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;10. Being listed on a blocklist&lt;/h3&gt;&lt;p&gt;If your sending domain or IP address appears on a public email blocklist, your messages may be blocked outright or automatically filtered to spam on any mail server that queries that list. &lt;/p&gt;&lt;p&gt;Blocklisting can happen because of previous poor sending behavior, because you shared an IP with a bad actor, or because your domain was spoofed in a phishing campaign without your knowledge.&lt;/p&gt;&lt;h3&gt;How to fix it, step by step:&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Step 1: Check your domain and sending IP regularly against blocklists using MXToolBox or MultiRBL. Check at minimum once per month and after any deliverability incident.&lt;/li&gt;&lt;li&gt;Step 2: If you find yourself listed, do not submit a removal request immediately. First identify and fix the root cause, whether that is a compromised account, poor list hygiene, or high complaint rates.&lt;/li&gt;&lt;li&gt;Step 3: Once the underlying issue is resolved, submit a delisting request to the blocklist operator. Most major lists have a self-service removal process.&lt;/li&gt;&lt;li&gt;Step 4: After removal, monitor your deliverability closely for two to four weeks to confirm the issue does not recur.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;The role of List quality in everything&lt;/h2&gt;&lt;p&gt;Almost every deliverability problem traces back to list quality at some level. Sending to addresses that did not opt in, keeping inactive subscribers, ignoring bounce data, and failing to process unsubscribes all create conditions that trigger spam filters repeatedly.&lt;/p&gt;&lt;p&gt;A clean, permission-based list with strong engagement signals is the most reliable foundation you can build. Just follow the &lt;a href=&quot;https://fluentcrm.com/blog/email-list-management-best-practices/&quot;&gt;email list management guide&lt;/a&gt; to keep a list healthy over time, including handling bounces, managing inactive contacts, and running proper re-engagement campaigns.&lt;/p&gt;&lt;p&gt;For teams handling high volumes of customer email, structured workflows also matter. Using a system like an &lt;a href=&quot;https://fluentsupport.com/email-ticketing-systems/&quot;&gt;email ticketing system&lt;/a&gt; helps support teams manage customer conversations in an organized way, which reduces the risk of sending patterns that appear spammy to mail providers due to inconsistent volume or disorganized routing.&lt;/p&gt;&lt;h2&gt;Transactional emails go to spam too&lt;/h2&gt;&lt;p&gt;A common misconception is that transactional emails, such as order confirmations, password resets, and account notifications, are immune to spam filters. They have better deliverability on average because they are expected and triggered by user action. &lt;/p&gt;&lt;p&gt;But they can still go to spam if your domain reputation is poor, your authentication is broken, or the email content resembles a phishing template.&lt;/p&gt;&lt;p&gt;The best practice is to separate your transactional and marketing email streams using different subdomains. For example, &lt;strong&gt;use mail.yourdomain.com&lt;/strong&gt; for marketing campaigns and &lt;strong&gt;notify.yourdomain.com&lt;/strong&gt; for transactional messages. &lt;/p&gt;&lt;p&gt;This isolation ensures that a spike in spam complaints from a marketing campaign does not drag down the delivery of your critical transactional messages. Many support and product teams handle this using &lt;a href=&quot;https://fluentsupport.com/what-is-email-piping-and-why/&quot;&gt;email piping&lt;/a&gt; to route replies and notifications cleanly through their infrastructure without mixing streams.&lt;/p&gt;&lt;h2&gt;Quick reference: spam fix checklist&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Use this after diagnosing your specific issue to confirm you have covered each area:&lt;/li&gt;&lt;li&gt;Verify that SPF, DKIM, and DMARC records are all present, correctly configured, and passing checks.&lt;/li&gt;&lt;li&gt;Remove hard bounces immediately after every send and keep your bounce rate below 0.5%.&lt;/li&gt;&lt;li&gt;Add a working one-click unsubscribe link to every commercial email.&lt;/li&gt;&lt;li&gt;Process all unsubscribe requests within 48 hours.&lt;/li&gt;&lt;li&gt;Remove or suppress subscribers who have not engaged in the past 90 days.&lt;/li&gt;&lt;li&gt;Audit your subject lines and email body for spam trigger words, all-caps text, and misleading copy.&lt;/li&gt;&lt;li&gt;Replace URL shorteners with tracking links on your own domain.&lt;/li&gt;&lt;li&gt;Check your HTML for broken markup before every send.&lt;/li&gt;&lt;li&gt;Check your domain and IP against blocklists at least once per month.&lt;/li&gt;&lt;li&gt;Increase sending volume gradually rather than in sudden spikes.&lt;/li&gt;&lt;li&gt;Add CAPTCHA or honeypot protection to all sign-up forms.&lt;/li&gt;&lt;li&gt;Send from a custom domain with full authentication, never a free email service.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;Emails going to spam is almost never caused by a single obvious mistake. It is usually a combination of signals, any one of which alone might not cause a problem, but together push your emails past the spam threshold. &lt;/p&gt;&lt;p&gt;Authentication is where to start. List quality is where to sustain progress. Engagement is what separates senders who recover from those who never fully do.&lt;/p&gt;&lt;p&gt;Work through each reason methodically, prioritize the ones that apply to your situation, and track your metrics over time. Deliverability rewards consistency more than any single fix.&lt;/p&gt;&lt;p&gt;Want a system that makes ticket handling this smooth? Fluent Support brings structure and clarity to every request so your team always knows exactly what to do next. See how it works.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://fluentsupport.com/features/&quot;&gt;&lt;span&gt;Explore Fluent Support Now!&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2&gt;FAQs&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;How do I check if my emails are going to spam without asking recipients? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Use seed testing tools to send your email to a network of test accounts across multiple providers and track placement. You can also use Google Postmaster Tools for Gmail-specific spam rate data. Many ESPs offer inbox placement testing built into their platform.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Does changing the subject line stop emails from going to spam? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The subject line is one content signal among many. A cleaner subject line helps, but if your domain reputation or authentication is broken, changing the subject line alone will not fix inbox placement. Address the technical and reputation issues first.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why are my emails going to spam in Gmail but not Outlook?&lt;/strong&gt; &lt;/p&gt;&lt;p&gt;Gmail and Outlook use different spam scoring algorithms with different thresholds and priorities. Gmail places heavy weight on engagement signals and has strict spam rate limits under its 2024 sender requirements. &lt;/p&gt;&lt;p&gt;Outlook prioritizes IP reputation and content filters more heavily. Check Google Postmaster Tools for your Gmail-specific domain reputation and spam rate data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;How long does it take to fix email deliverability issues? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Authentication fixes are immediate. Reputation recovery depends on how damaged the reputation is. Minor issues can resolve in a few weeks. &lt;/p&gt;&lt;p&gt;Significant damage from high complaint rates or blocklisting may take two to three months of consistent, clean sending before inbox placement fully normalizes.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why are my outreach emails going to spam even when they are personalized? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Cold outreach emails face higher scrutiny because recipients did not opt in. Even well-personalized cold emails get flagged due to domain age, low sender reputation, or content patterns associated with sales prospecting. &lt;/p&gt;&lt;p&gt;Warming your domain gradually, sending low initial volumes, and earning genuine replies all help build the reputation that improves placement over time.&lt;/p&gt;&lt;p&gt;The post &lt;a href=&quot;https://fluentsupport.com/why-are-my-emails-going-to-spam/&quot;&gt;Why Are My Emails Going to Spam? Reasons and How to Fix Them&lt;/a&gt; appeared first on &lt;a href=&quot;https://fluentsupport.com&quot;&gt;Fluent Support&lt;/a&gt;.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>HTTP Error 521: What It Means and How to Fix It Fast</title>
<link>https://fluentsupport.com/http-error-521-fix/</link>
<guid isPermaLink="false">JbYyIntwqwYolw_jBWiOHV9rlRyY0EvgDwqKGg==</guid>
<pubDate>Sat, 16 May 2026 11:17:45 +0000</pubDate>
<description>HTTP error 521 means Cloudflare can&#39;t connect to your origin server. Learn what causes it and how to fix it to get your site back online. The post HTTP Error 521: What It Means and How to Fix It Fast appeared first on Fluent Support.</description>
<content:encoded>&lt;p&gt;Your site is live, Cloudflare is active, and then out of nowhere visitors see a blank page that reads “Web server is down.” No warning. No obvious reason. Just a broken connection and lost traffic.&lt;/p&gt;&lt;p&gt;HTTP error 521 is one of those errors that catches site owners off guard because everything on the surface looks fine. Cloudflare is up. Your domain is resolving. But somewhere between the CDN and your origin server, something went wrong.&lt;/p&gt;&lt;p&gt;In this blog, we will cover what HTTP error 521 actually means, what causes it, how to fix it step by step, and how to prevent it from coming back.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;TL;DR&lt;/p&gt;&lt;ul&gt;&lt;li&gt;HTTP error 521 means Cloudflare reached your server but the server refused the connection&lt;/li&gt;&lt;li&gt;The problem is always on the origin server side, not Cloudflare’s&lt;/li&gt;&lt;li&gt;Common causes: server is offline, firewall blocking Cloudflare IPs, wrong SSL/TLS settings, or incorrect port binding&lt;/li&gt;&lt;li&gt;Fixes include allowlisting Cloudflare IPs, checking server status, and aligning your SSL mode&lt;/li&gt;&lt;li&gt;Site visitors cannot fix this themselves, it requires action from the site owner or host&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;What Is HTTP Error 521?&lt;/h2&gt;&lt;p&gt;HTTP error 521 is a Cloudflare-specific status code that appears when Cloudflare successfully receives a visitor’s request but cannot establish a TCP connection to your origin web server. The server actively refuses the connection rather than simply timing out.&lt;/p&gt;&lt;p&gt;According to &lt;a href=&quot;https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-521/&quot;&gt;Cloudflare’s official documentation&lt;/a&gt;, “the two most common causes are an offlined origin web server application and blocked Cloudflare requests.” The error message visitors see typically reads: “Error 521: Web server is down.”&lt;/p&gt;&lt;p&gt;The key distinction here matters. HTTP error 521 is not a Cloudflare failure. Cloudflare is doing its job. The issue sits on your hosting side, which is why your host may tell you “everything looks fine” when you contact them while Cloudflare is still proxying the requests.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/1.-Cloudflare-Error-521-Web-server-is-down-Fluent-Support-Blog.webp&quot; alt=&quot;Cloudflare Error 521, “Web server is down”, Fluent Support Blog&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h2&gt;How Cloudflare Works (And Why 521 Happens)&lt;/h2&gt;&lt;p&gt;Cloudflare operates as a reverse proxy. When a visitor requests your site, they connect to Cloudflare’s edge servers first. Cloudflare then forwards that request to your origin server and returns the response to the visitor.&lt;/p&gt;&lt;p&gt;In a working setup, this happens in milliseconds. With error 521, the handshake breaks at the second step. Cloudflare tries to open a connection on port 80 (HTTP) or port 443 (HTTPS) and gets a “connection refused” response. The origin server is essentially saying no.&lt;/p&gt;&lt;p&gt;This matters for business owners beyond the technical details. Downtime carries real financial cost. According to a Gartner study cited by &lt;a href=&quot;https://www.atlassian.com/incident-management/kpis/cost-of-downtime&quot;&gt;Atlassian’s incident management research&lt;/a&gt;, “the average cost of IT downtime is $5,600 per minute.” For ecommerce stores or SaaS products, even a few minutes of HTTP 521 errors can mean lost orders and damaged customer trust.&lt;/p&gt;&lt;h2&gt;What Causes HTTP Error 521?&lt;/h2&gt;&lt;p&gt;Understanding the cause narrows down your fix quickly. There are four primary culprits.&lt;/p&gt;&lt;p&gt;Your web server process is down. Apache, Nginx, or whatever web server software you use may have crashed or stopped. The physical server machine itself could still be running, but if the application process stopped, Cloudflare has nothing to connect to.&lt;/p&gt;&lt;p&gt;A firewall is blocking Cloudflare IPs. Because Cloudflare proxies all traffic through its own IP ranges, your origin server receives requests from Cloudflare IPs rather than individual visitor IPs. Security tools like Fail2Ban, iptables, or server-level firewalls can mistakenly flag these as suspicious and block them. As confirmed in &lt;a href=&quot;https://community.cloudflare.com/t/community-tip-fixing-error-521-web-server-is-down/42461&quot;&gt;Cloudflare’s community troubleshooting tip&lt;/a&gt;, “this is one of the most frequently seen causes of error 521.”&lt;/p&gt;&lt;p&gt;SSL/TLS misconfiguration. If your Cloudflare SSL mode is set to Full or Full (Strict) but your origin server has no valid SSL certificate installed, Cloudflare cannot complete a secure handshake. The connection gets refused before any content is served.&lt;/p&gt;&lt;p&gt;Incorrect port binding. Your server may not be listening on port 80 or 443. Cloudflare expects these standard ports. If your web server process is bound to a different port or not bound at all, the connection fails at the TCP layer.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/2.-Cloudflare-Proxy-Flow-Where-Error-521-Occurs-Fluent-Support-Blog.webp&quot; alt=&quot;Cloudflare Proxy Flow &amp;amp; Where Error 521 Occurs, Fluent Support Blog&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h2&gt;How to Fix HTTP Error 521: Step by Step&lt;/h2&gt;&lt;h3&gt;1. Check Whether Your Web Server Is Running&lt;/h3&gt;&lt;p&gt;Start with the basics. SSH into your server and run a status check on your web server process.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;For Nginx:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;systemctl status nginx&lt;/p&gt;&lt;p&gt;&lt;strong&gt;For Apache:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;systemctl status apache2&lt;/p&gt;&lt;p&gt;If the service shows as inactive or failed, restart it:&lt;/p&gt;&lt;p&gt;systemctl restart nginx&lt;/p&gt;&lt;p&gt;You can also use the curl command to test a direct connection to your server’s IP address, bypassing Cloudflare entirely. &lt;/p&gt;&lt;p&gt;If the server responds directly but not through Cloudflare, the problem points toward firewall or configuration issues rather than the server being fully offline.&lt;/p&gt;&lt;h3&gt;2. Allowlist Cloudflare IP Addresses&lt;/h3&gt;&lt;p&gt;Cloudflare publishes its complete list of IP ranges at &lt;a href=&quot;https://www.cloudflare.com/ips/&quot;&gt;cloudflare.com/ips&lt;/a&gt;. You need to ensure that all of these addresses are explicitly permitted in your firewall rules.&lt;/p&gt;&lt;p&gt;In your .htaccess file for Apache, add allow from directives for each Cloudflare IP range. For iptables on Linux, use the appropriate accept rules. If you use a hosting control panel like cPanel or a security tool like Fail2Ban, check the whitelist or allowlist settings there.&lt;/p&gt;&lt;p&gt;This step resolves the majority of HTTP 521 errors, particularly those that appear intermittently or only under certain traffic conditions.&lt;/p&gt;&lt;h3&gt;3. Align Your SSL/TLS Settings&lt;/h3&gt;&lt;p&gt;In your Cloudflare dashboard, go to the SSL/TLS section and check which encryption mode is active.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Flexible:&lt;/strong&gt; Cloudflare connects to your origin over HTTP. No certificate is required on the origin server.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Full:&lt;/strong&gt; Cloudflare connects over HTTPS. A certificate must be installed on the origin, but it can be self-signed.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Full (Strict):&lt;/strong&gt; Requires a valid, trusted certificate on the origin server, either from a certificate authority or a Cloudflare Origin Certificate.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you are using Full or Full (Strict) without a valid certificate on your origin, the SSL handshake will fail and trigger error 521. Either install a certificate or change the SSL mode to match your actual server setup.&lt;/p&gt;&lt;p&gt;Cloudflare’s free Origin Certificates, available directly through the SSL/TLS section of your dashboard under “Origin Server,” are the cleanest fix for most shared hosting or VPS environments.&lt;/p&gt;&lt;h3&gt;4. Confirm Port Binding&lt;/h3&gt;&lt;p&gt;Your server must be actively listening on the port Cloudflare expects. For Flexible SSL mode, that is port 80. For Full or Full (Strict), that is port 443.&lt;/p&gt;&lt;p&gt;Run the following command to check what your server is listening on:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;netstat -tlnp | grep -E ’80|443′&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;If neither port shows your web server process in the output, the binding is missing or the process has crashed.&lt;/p&gt;&lt;h3&gt;5. Review Server Error Logs&lt;/h3&gt;&lt;p&gt;When the cause is not immediately obvious, your server logs usually reveal it. For Nginx, check /var/log/nginx/error.log. For Apache, check /var/log/apache2/error.log. Look for connection refusals, crash reports, or resource exhaustion messages around the time the error first appeared.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/3.-sample-Nginx-error-log-with-a-connection-refused-entry-Fluent-Support-Blog.webp&quot; alt=&quot;sample Nginx error log with a connection refused entry, Fluent Support Blog&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h3&gt;6. Temporarily Disable Cloudflare to Isolate the Problem&lt;/h3&gt;&lt;p&gt;If you need to confirm whether the issue lies with your Cloudflare configuration or the origin server itself, pause Cloudflare from the Overview section of your dashboard. Once paused, traffic goes directly to your origin. If the site loads normally, the issue is in your Cloudflare settings. If it still fails, the origin server needs direct attention.&lt;/p&gt;&lt;h2&gt;HTTP Error 521 in Specific Environments&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Ubuntu servers:&lt;/strong&gt; The fix usually involves verifying whether Nginx or Apache is running via systemctl status, then reviewing iptables rules to confirm Cloudflare IP ranges are not being blocked at the OS level.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Nginx setups:&lt;/strong&gt; Pay close attention to the listen directives in your Nginx configuration files. If the server block is only configured to listen on a non-standard port, Cloudflare’s connection attempts on 80 or 443 will be refused every time.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;WordPress sites:&lt;/strong&gt; Security plugins like Wordfence can build firewall rules that inadvertently block Cloudflare’s proxy IPs. Temporarily deactivating security plugins and retesting can confirm whether this is the cause. Resource-heavy plugins can also push the server into overload, causing it to stop accepting new connections entirely.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Firestick and streaming apps (Tivimate, Mihon, etc.):&lt;/strong&gt; When users report HTTP 521 on these platforms, the error is originating from the content server those apps connect to. The app itself is not at fault. There is nothing the end user can do. The server operator on the other side needs to resolve it.&lt;/p&gt;&lt;h2&gt;How This Connects to Customer Support Workflows&lt;/h2&gt;&lt;p&gt;HTTP error 521 is not only a technical problem. For businesses selling products or services online, server downtime directly affects customer experience and trust.&lt;/p&gt;&lt;p&gt;When users hit error 521, they reach out through email, social media, and support channels looking for answers. Having a reliable &lt;a href=&quot;https://fluentsupport.com/what-is-helpdesk-ticketing-system-and-how-does-it-work/&quot;&gt;support ticket system&lt;/a&gt; means those reports get captured and routed to the right team quickly. Without one, messages get buried in inboxes and the problem lingers longer than it should.&lt;/p&gt;&lt;p&gt;A solid &lt;a href=&quot;https://fluentsupport.com/customer-service-workflows/&quot;&gt;customer service workflow&lt;/a&gt; should include a step for outage communication. When your site goes down, customers need a timely and clear message. Proactive communication during downtime is one of the most effective ways to preserve trust while the technical fix is in progress. Fluent Support’s guide on &lt;a href=&quot;https://fluentsupport.com/proactive-customer-support/&quot;&gt;proactive customer support&lt;/a&gt; covers this in more depth.&lt;/p&gt;&lt;p&gt;Related server errors like &lt;a href=&quot;https://fluentsupport.com/502-bad-gateway-fix/&quot;&gt;502 Bad Gateway&lt;/a&gt; and &lt;a href=&quot;https://fluentsupport.com/http-error-503/&quot;&gt;HTTP error 503&lt;/a&gt; share overlapping diagnostic steps with 521, so having your support team familiar with this category of errors reduces resolution time across the board.&lt;/p&gt;&lt;h2&gt;How to Prevent HTTP Error 521 from Recurring&lt;/h2&gt;&lt;p&gt;Once you resolve the immediate issue, a few habits keep it from returning.&lt;/p&gt;&lt;p&gt;Set up uptime monitoring using a tool like UptimeRobot so you get alerted the moment your server becomes unreachable, rather than hearing about it from customers first. Always keep Cloudflare’s full IP list allowlisted and review your firewall rules whenever you install new security software or change hosting configurations.&lt;/p&gt;&lt;p&gt;If your site handles regular traffic spikes, consider upgrading your hosting plan before you hit the resource limits that cause web server processes to crash under load. Keeping your SSL certificates renewed and your Cloudflare SSL mode consistently aligned with your origin setup eliminates one of the most avoidable causes of recurring 521 errors.&lt;/p&gt;&lt;h2&gt;Wrapping Up&lt;/h2&gt;&lt;p&gt;HTTP error 521 points to one specific breakdown: Cloudflare tried to reach your origin server, and the server refused the connection. &lt;/p&gt;&lt;p&gt;The root cause is almost always one of four things, a stopped web server process, Cloudflare IPs being blocked by a firewall, an SSL configuration mismatch, or a port binding issue.&lt;/p&gt;&lt;p&gt;Work through the fixes in order. Start with server status, move to firewall rules, then SSL settings, then port binding. Check your server error logs at any stage if the cause remains unclear.&lt;/p&gt;&lt;p&gt;For visitors who encounter this error, there is no client-side workaround. The resolution sits entirely with the site owner or hosting provider.&lt;/p&gt;&lt;p&gt;Want a system that makes ticket handling this smooth? Fluent Support brings structure and clarity to every request so your team always knows exactly what to do next. See how it works.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://fluentsupport.com/features/&quot;&gt;&lt;span&gt;Explore Fluent Support Now!&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2&gt;Frequently Asked Questions&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;What is HTTP error 521 Cloudflare? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;It is a status code Cloudflare returns when its edge servers cannot establish a TCP connection to your origin server. The server is actively refusing the connection rather than timing out or being unreachable on the network.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;How do I fix error 521 on my web server? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Start by confirming your web server process (Nginx or Apache) is running. Then verify that all Cloudflare IP ranges listed at&lt;a href=&quot;https://www.cloudflare.com/ips/&quot;&gt; cloudflare.com/ips&lt;/a&gt; are not blocked by your firewall. Finally, check that your Cloudflare SSL/TLS mode matches your origin server’s certificate configuration.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What does HTTP 521 mean on Mihon or Tivimate? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;On streaming or manga reader apps, error 521 means the content server those apps connect to is experiencing an origin server refusal of Cloudflare’s connection. The app itself is not the cause. Only the server operator can resolve it.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Can error 521 hurt my SEO? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Yes. If search engine crawlers consistently encounter 521 errors, it signals that the site is unreliable. Extended downtime can lead to ranking drops. Fixing the error promptly and using uptime monitoring to catch future occurrences protects both rankings and user trust.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is the difference between error 521 and error 522? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;HTTP error 521 means the connection was actively refused by the origin server the moment Cloudflare attempted it.&lt;a href=&quot;https://fluentsupport.com/error-522-meaning-and-how-to-fix/&quot;&gt; Error 522&lt;/a&gt; means the connection was initiated but the origin server failed to respond within Cloudflare’s timeout window. Both point to origin server problems but represent different failure modes at the network level.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What does “Web server is down error code 521 Nginx” mean? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This refers specifically to Nginx as the web server process that has stopped or refused the incoming connection. The fix typically involves restarting the Nginx service with systemctl restart nginx and checking your Nginx configuration for correct port binding on port 80 or 443.&lt;/p&gt;&lt;p&gt;The post &lt;a href=&quot;https://fluentsupport.com/http-error-521-fix/&quot;&gt;HTTP Error 521: What It Means and How to Fix It Fast&lt;/a&gt; appeared first on &lt;a href=&quot;https://fluentsupport.com&quot;&gt;Fluent Support&lt;/a&gt;.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>How to Submit URLs to Google for Indexing (4 Methods)</title>
<link>https://fluentsupport.com/submit-urls-to-google/</link>
<guid isPermaLink="false">gNBqhIzV0dGCWCxsdSAEacGUKDzZDbTu1Wgt0w==</guid>
<pubDate>Sat, 16 May 2026 11:17:44 +0000</pubDate>
<description>Submit a URL to Google using Search Console&#39;s URL Inspection Tool, a sitemap, or internal links. Here&#39;s how each method works. The post How to Submit URLs to Google for Indexing (4 Methods) appeared first on Fluent Support.</description>
<content:encoded>&lt;p&gt;Publishing a page does not mean Google knows it exists. Until Google crawls and indexes your URL, it will not appear in any search result, no matter how good the content is.&lt;/p&gt;&lt;p&gt;That gap between publishing and getting indexed is where a lot of traffic gets lost. Submitting your URL to Google closes that gap faster than waiting for Googlebot to stumble across it on its own.&lt;/p&gt;&lt;p&gt;This guide covers every practical method for submitting URLs to Google, what to do when indexing still does not happen, and the mistakes that quietly slow the whole process down.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;TL;DR&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What does submitting a URL to Google mean?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Submitting a URL to Google is a manual signal that tells Googlebot to crawl a specific page and consider adding it to the search index. It speeds up discovery but does not guarantee the page will be indexed.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What are the methods for submitting URLs to Google?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The four main methods are the URL Inspection Tool in Google Search Console, sitemap submission, internal linking from already-indexed pages, and the Google Indexing API for automated or high-volume needs.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;How do you use the URL Inspection Tool?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Log in to Google Search Console, paste the URL into the search bar at the top, wait for Google to check its index status, then click ‘Request Indexing.’ Google will run a live test and add the URL to its priority crawl queue.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why would a submitted URL still not get indexed?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The most common reasons are a noindex tag on the page, a robots.txt block preventing crawling, thin or duplicate content, a canonical tag pointing to a different URL, or crawl budget limitations. The URL Inspection Tool surfaces most of these issues in its live test report.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What best practices should I follow when submitting URLs?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Only submit pages with substantive, original content. Fix any technical issues before submitting. Ensure your site uses HTTPS. Do not resubmit the same URL repeatedly. Keep pages mobile-friendly since Google uses mobile-first indexing.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;What Does Submitting a URL to Google Actually Do?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Submitting a URL to Google is a manual signal that tells Googlebot to crawl a specific page and consider adding it to the search index. It does not guarantee indexing, and it does not influence rankings. What it does is move your page into Google’s priority crawl queue, which speeds up the discovery process compared to waiting for natural crawling.&lt;/p&gt;&lt;p&gt;Without submission, Google finds pages through links, sitemaps, or revisiting previously crawled pages. For new sites, pages with few backlinks, or freshly updated content, that natural process can take days to weeks. Submitting directly cuts that waiting time significantly.&lt;/p&gt;&lt;h2&gt;Methods to submit URLs to Google&lt;/h2&gt;&lt;p&gt;There are four reliable ways to submit URLs to Google for indexing:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Use the URL Inspection Tool in Google Search Console&lt;/li&gt;&lt;li&gt;Submit a sitemap through Google Search Console&lt;/li&gt;&lt;li&gt;Add internal links from already-indexed pages&lt;/li&gt;&lt;li&gt;Use the Google Indexing API for automated bulk submission&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Each method serves a different situation. The sections below break down exactly how to use each one.&lt;/p&gt;&lt;h3&gt;Method 1: Use Google Search Console’s URL Inspection Tool&lt;/h3&gt;&lt;p&gt;The URL Inspection Tool in Google Search Console is the easiest way to submit your URLs to Google. It’s like handing Google a VIP ticket to your latest content.&lt;br/&gt;&lt;br/&gt;Here’s how to use it step by step:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Log in to Google Search Console. (If you don’t have an account yet, don’t worry. I’ve included instructions in the note below. It’s totally free and straightforward)&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Paste your URL into the search bar at the top of the dashboard. Or you can click on the URL inspection and paste it. &lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2025/02/Google-URL-inspection-tool-1024x340.webp&quot; alt=&quot;Google URL inspection tool&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; The tool will check if your URL is already indexed. If it’s not, you’ll see a “Request Indexing” button. Click it, and you’re done!&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2025/02/URL-is-not-on-Google-request-indexing-1024x294.webp&quot; alt=&quot;URL is not on Google (request indexing)&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;p&gt;A shout-out for Google Search Console. This inspection tool is super efficient. It lets Google know about your pages almost instantly.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: How to sign up for Google Search Console and add your site&lt;/p&gt;&lt;p&gt;First, go to Google Search Console. Then click the “Start Now” button and log in with your Google account. Then, add your website with the URL prefix. Now, it’s time to &lt;a href=&quot;https://support.google.com/webmasters/answer/9008080?sjid=8015725356720956866-AP&quot;&gt;verify ownership&lt;/a&gt; by following one of the provided methods: HTML file, HTML tag, Google Analytics, Google Tag Manager, and Domain name provider.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;And done! You can now unlock all the features of Google Search Console (well, well, you have hired a dedicated website assistant).&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Method 2: &lt;/strong&gt;Submit a sitemap through Google Search Console&lt;/h3&gt;&lt;p&gt;If the URL Inspection Tool is your one-on-one chat with Google, submitting a sitemap is like sending Google a formal invitation with all your website’s details.&lt;/p&gt;&lt;p&gt;With sitemap submission, you let Google discover multiple URLs at once. This could be a great time saver, especially when managing a large site.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: A sitemap is an XML file that informs search engines about all your important pages. It helps Google and other search engine crawlers to find and index your content more efficiently.&lt;/p&gt;&lt;p&gt;To submit a sitemap, follow these instructions:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Create a sitemap. If you’re using WordPress, plugins like &lt;a href=&quot;https://yoast.com/&quot;&gt;Yoast SEO&lt;/a&gt; or &lt;a href=&quot;https://rankmath.com/&quot;&gt;Rank Math&lt;/a&gt; can generate one automatically. Or you can create your sitemap with another plugin &lt;a href=&quot;https://wordpress.org/plugins/google-sitemap-generator/&quot;&gt;XML Sitemap Generator for Google&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Again, if you are a non-WordPress user, online &lt;a href=&quot;https://www.xml-sitemaps.com/&quot;&gt;sitemap generator&lt;/a&gt; tools could be a good option.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Locate your sitemap URL. It usually looks something like this: &lt;strong&gt;https://yourwebsite.com/sitemap_index.xml.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Go to Google Search Console and navigate to the Sitemaps section (it’s in the ‘Indexing’ tab).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Paste the sitemap URL into the provided field and hit “Submit.”&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2025/02/Submit-site-map-to-Google-1024x247.webp&quot; alt=&quot;Submit sitemap to Google&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;p&gt;And that’s it!&lt;/p&gt;&lt;p&gt;You’ve handed Google a comprehensive map of your website.&lt;/p&gt;&lt;p&gt;Easy-peasy.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Method 3: Internal Linking from Indexed Pages&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Internal links are one of the most underused methods for getting new pages crawled. When you add a link to a new page from an existing, already-indexed page, Googlebot follows that link during its next crawl of the source page and discovers the new URL naturally.&lt;/p&gt;&lt;p&gt;This matters especially for pages with limited external backlinks. A page that exists only in your sitemap or is completely isolated has low crawl priority. Linking to it from a high-traffic or frequently crawled page on your site raises its discoverability significantly.&lt;/p&gt;&lt;p&gt;The best places to add internal links to a new page are your homepage, relevant category pages, high-traffic blog posts on related topics, and any hub pages that aggregate content on the same subject. Use descriptive anchor text that reflects what the new page is actually about.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Method 4: Google Indexing API (For High-Volume or Automated Needs)&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;The Google Indexing API lets you notify Google programmatically when a URL has been added or removed. It was originally built for job posting and livestream pages, but many site owners use it more broadly for faster indexing of any content type.&lt;/p&gt;&lt;p&gt;Setting it up requires a Google Cloud project, a service account with Search Console owner permissions, and some basic API calls. Tools like Rank Math’s Instant Indexing plugin for WordPress wrap this API into a one-click setup, making it accessible without developer knowledge.&lt;/p&gt;&lt;p&gt;This method is worth considering if you publish large volumes of content regularly and need indexing to happen faster than manual requests allow. For smaller sites or occasional new posts, the URL Inspection Tool and sitemap approach are sufficient.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Why Your Page Still Is Not Indexed After Submission&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Submitting a URL does not guarantee indexing. Google makes its own decision about whether a page belongs in the index, based on quality signals, technical factors, and crawl budget. These are the most common reasons a submitted URL fails to get indexed.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;&lt;strong&gt;The page has a noindex tag&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;A noindex directive in the page’s meta robots tag or HTTP header tells Google explicitly not to index it. This overrides any submission request. Check your page’s source code for &lt;mark&gt;&amp;lt;meta name=”robots” content=”noindex”&amp;gt;&lt;/mark&gt; or use the URL Inspection Tool to see what Google found during its live test.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;&lt;strong&gt;The page is blocked by robots.txt&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;If your robots.txt file disallows Googlebot from crawling the URL or the directory it lives in, Google cannot access the page to index it. Submitting the URL will not override a robots.txt block. Check your robots.txt at &lt;mark&gt;yourdomain.com/robots.txt&lt;/mark&gt; and confirm the page’s path is not listed under Disallow rules.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;&lt;strong&gt;The content is too thin or duplicated&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Google actively filters out pages it considers low-quality, repetitive, or near-duplicate versions of content that already exists in its index. If your page is short, largely templated, or closely mirrors another URL on your site, it may be crawled but not indexed. The URL Inspection Tool will sometimes show this as ‘Crawled, currently not indexed’ in the coverage report.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;&lt;strong&gt;Canonical tag points to a different URL&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;A canonical tag signals to Google which URL should be treated as the authoritative version of a page. If your canonical points to a different URL, Google will index that other URL, not the one you submitted. This is common on sites with URL parameters, pagination, or staging-to-production migration issues that were not cleaned up.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;&lt;strong&gt;Crawl budget limitations&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Google allocates a crawl budget to each site based on its authority, server response times, and the quality of pages it has already found. Sites with a large number of low-value pages, slow load times, or frequent crawl errors can exhaust their crawl budget before Google reaches important new content. Keeping your site lean, fast, and technically clean is the long-term fix here.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;Best Practices for Submitting URLs to Google&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Submitting URLs correctly makes a measurable difference in how quickly and reliably Google indexes your content. These practices keep the process clean and efficient.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Only submit pages worth indexing&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Not every page on your site deserves to be in Google’s index. Thank-you pages, account pages, duplicate filtered views, and thin content pages add noise to your crawl budget without contributing any search value. Submit only pages with original, substantive content that you want to appear in search results.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Fix technical issues before submitting&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;A page submitted with broken links, slow load times, missing canonical tags, or redirect chains is going to have indexing problems regardless of how the submission is made. Run a quick technical check on any page before requesting indexing. The URL Inspection Tool itself surfaces many of these issues in its live test report.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Use HTTPS&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Google treats HTTPS as a baseline requirement for trustworthy, rankable pages. If your site still serves pages over HTTP, those pages are at a disadvantage both in indexing priority and in ranking potential. Migrating to HTTPS before submitting URLs removes one more potential barrier.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Do not re-submit the same URL repeatedly&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Submitting a URL once adds it to Google’s crawl queue. Submitting it again immediately does not move it higher in that queue or accelerate the process. If a page has not been indexed after a week, the issue is almost certainly a quality or technical problem, not a submission problem. Use the URL Inspection Tool to investigate rather than resubmitting blindly.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Keep pages mobile-friendly&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Google uses &lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing&quot;&gt;mobile-first indexing&lt;/a&gt;, which means it primarily crawls and indexes the mobile version of your pages. A page that renders poorly on mobile, loads slowly on a phone, or has content differences between mobile and desktop versions will run into indexing and ranking issues. Test your pages with &lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing&quot;&gt;Google’s mobile-friendly test&lt;/a&gt; before submitting high-priority URLs.&lt;/p&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;The gap between publishing and getting indexed is a real problem. Most of the time it is not a submission problem. It is a quality problem, a technical problem, or a crawl priority problem wearing a submission problem’s mask.&lt;/p&gt;&lt;p&gt;Submit your URLs through Search Console, keep your sitemap updated, link to new pages from existing ones, and make sure every page you submit is technically clean and worth indexing. The URL Inspection Tool will tell you most of what you need to know if something is not moving.&lt;/p&gt;&lt;p&gt;Google indexes what it trusts. Give it a reason to trust your pages first, and the indexing follows.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;strong&gt;Tired of buying addons for your premium helpdesk&lt;/strong&gt;?&lt;/span&gt;&lt;p&gt;Start off with a powerful ticketing system that delivers smooth collaboration right out of the box.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://fluentsupport.com/pricing/&quot;&gt;&lt;span&gt;&lt;strong&gt;Go Pro!&lt;/strong&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;Frequently Asked Questions&lt;/strong&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;strong&gt;&lt;strong&gt;Does submitting a URL to Google guarantee indexing?&lt;/strong&gt;&lt;/strong&gt;&lt;p&gt;No. Submitting a URL adds it to Google’s priority crawl queue, but Google still decides whether to index the page based on its quality, technical health, and relevance. Pages that are thin, duplicated, blocked by robots.txt, or marked noindex will not be indexed regardless of submission.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;strong&gt;How long does it take Google to index a submitted URL?&lt;/strong&gt;&lt;/strong&gt;&lt;p&gt;After a manual submission via the URL Inspection Tool, indexing typically happens within a few hours to a few days. In some cases it can take up to a couple of weeks, especially for newer sites with low authority or pages with limited internal links pointing to them.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;strong&gt;Is there a limit to how many URLs I can submit per day?&lt;/strong&gt;&lt;/strong&gt;&lt;p&gt;Yes. The URL Inspection Tool has a daily quota for manual indexing requests. The exact limit is not publicly specified by Google, but users typically see quota warnings after submitting a large number of URLs in a short window. For bulk submissions, the sitemap method or Google Indexing API is more appropriate.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;strong&gt;Can I submit a URL to Google without Google Search Console?&lt;/strong&gt;&lt;/strong&gt;&lt;p&gt;Without Search Console, your primary options are submitting a sitemap hosted on your server that Googlebot can discover, building internal and external links to the page, and relying on natural crawling. Search Console’s URL Inspection Tool is the most reliable manual method, and setting it up is free.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;strong&gt;Why is my page showing ‘Crawled, currently not indexed’ in Search Console?&lt;/strong&gt;&lt;/strong&gt;&lt;p&gt;This status means Google found and crawled the page but chose not to add it to the index. The most common reasons are thin or low-quality content, near-duplicate content that already exists elsewhere in the index, or a canonical conflict where Google determined another URL should be the authoritative version. Improving the content quality and resolving any canonical issues is the recommended approach.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;strong&gt;Should I resubmit a URL if it has not been indexed after a week?&lt;/strong&gt;&lt;/strong&gt;&lt;p&gt;Not immediately. If a page has not been indexed after a week, resubmitting it will not change the outcome. The issue is almost always a content quality or technical problem. Use the URL Inspection Tool to run a live test, check the coverage report for specific error reasons, and fix whatever is flagged before submitting again.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;strong&gt;What is the difference between crawling and indexing?&lt;/strong&gt;&lt;/strong&gt;&lt;p&gt;Crawling is when Googlebot visits a URL to read its content. Indexing is when Google processes and stores that content in its search database so it can appear in results. A page can be crawled without being indexed if Google determines it does not meet its quality standards or if technical signals prevent inclusion.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://fluentsupport.com/submit-urls-to-google/&quot;&gt;How to Submit URLs to Google for Indexing (4 Methods)&lt;/a&gt; appeared first on &lt;a href=&quot;https://fluentsupport.com&quot;&gt;Fluent Support&lt;/a&gt;.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Error 522 Connection Timed Out: What It Means and How to Fix It - Fluent Support</title>
<link>https://fluentsupport.com/error-522-meaning-and-how-to-fix/</link>
<enclosure type="image/jpeg" length="0" url="https://fluentsupport.com/wp-content/uploads/2026/05/Error-522-Connection-Timed-Out_-What-It-Means-and-How-to-Fix-It-Fluent-Support-Blog-Featured-Image.webp"></enclosure>
<guid isPermaLink="false">zPJ3mxFWqpfx8_7bGjz2O4RZNT4rAQon1-ew1w==</guid>
<pubDate>Sat, 16 May 2026 11:17:44 +0000</pubDate>
<description>Error 522 means Cloudflare timed out connecting to your server. Learn the real causes and fixes to get your site back online fast.</description>
<content:encoded>&lt;p&gt;When a visitor hits your site and sees “Error 522 Connection Timed Out,” the problem is clear: your website is unreachable. The issue has nothing to do with your visitor’s internet connection. &lt;/p&gt;&lt;p&gt;It has nothing to do with Cloudflare’s infrastructure either. The problem lives on your origin server, and until you fix it, every visitor trying to reach your site lands on that same error page.&lt;/p&gt;&lt;p&gt;In this blog, we will walk through what error 522 actually means, what causes it, and the exact steps to resolve it and prevent it from coming back.&lt;/p&gt;&lt;div&gt;
&lt;div&gt;
&lt;p&gt;TL;DR&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;Error 522 is a Cloudflare-specific HTTP status code triggered when the connection between Cloudflare and your origin server times out&lt;/li&gt;



&lt;li&gt;The cause is almost always server-side: overload, firewall rules blocking Cloudflare IPs, wrong DNS settings, or disabled KeepAlive&lt;/li&gt;



&lt;li&gt;Fixes include whitelisting Cloudflare IPs, verifying your A record, enabling KeepAlive, and checking server resource usage&lt;/li&gt;



&lt;li&gt;It is different from error 521 (server actively refusing connections) and error 524 (server connected but responded too slowly)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;What Is Error 522?&lt;/h2&gt;&lt;p&gt;Error 522 is a Cloudflare-specific HTTP status code. It appears when Cloudflare cannot complete a TCP handshake with your origin web server within its timeout window. &lt;/p&gt;&lt;p&gt;Cloudflare introduced this code to describe one specific failure: the connection between its edge network and your server did not go through.&lt;/p&gt;&lt;p&gt;When a visitor loads your site, their browser talks to Cloudflare first. Cloudflare then reaches out to your origin server to fetch the content. This involves a TCP handshake, a three-step process (SYN, SYN-ACK, ACK) that establishes the connection. &lt;/p&gt;&lt;p&gt;If your server does not return a SYN-ACK within 19 seconds, or fails to acknowledge the resource request within 90 seconds after the connection is established, Cloudflare gives up and returns error 522 to the visitor.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/error-code-522-fluent-support-blog-how-to-fix-error-522.webp&quot; alt=&quot;error code 522, fluent support blog, how to fix error 522&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;p&gt;The visitor’s connection to Cloudflare completed successfully. The breakdown happened between Cloudflare and your server.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/three-step-TCP-handshake-flow-fluent-support-blog-error-522-1024x683.webp&quot; alt=&quot;three-step TCP handshake flow, fluent support blog, error 522&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h2&gt;What causes Error 522?&lt;/h2&gt;&lt;h3&gt;1. Server overload&lt;/h3&gt;&lt;p&gt;This is the most common cause. When your server is maxed out on CPU or RAM during traffic spikes, it cannot accept new incoming connections. Cloudflare sends a request, but the server has no capacity to respond. &lt;/p&gt;&lt;p&gt;Shared hosting plans are especially vulnerable because resources are split across multiple websites on the same machine.&lt;/p&gt;&lt;h3&gt;2. Firewall blocking Cloudflare IPs&lt;/h3&gt;&lt;p&gt;Cloudflare acts as a reverse proxy. All traffic that reaches your origin server comes from Cloudflare’s IP ranges rather than individual visitors’ addresses. &lt;/p&gt;&lt;p&gt;If your server’s firewall or a security plugin treats those IPs as suspicious and drops the packets, your server never responds. Cloudflare then times out waiting.&lt;/p&gt;&lt;h3&gt;3. Incorrect DNS settings&lt;/h3&gt;&lt;p&gt;Your Cloudflare DNS must have an A record that points to the correct IP address of your origin server. Server migrations and hosting provider IP changes are common reasons this goes wrong. &lt;/p&gt;&lt;p&gt;When the A record is stale, Cloudflare sends requests to an address that either does not exist or belongs to a different server entirely.&lt;/p&gt;&lt;h3&gt;4. KeepAlive disabled&lt;/h3&gt;&lt;p&gt;Cloudflare uses persistent TCP connections through KeepAlive headers. When a server closes the connection after every single request, Cloudflare has to re-establish a fresh connection each time. &lt;/p&gt;&lt;p&gt;That repeated overhead increases timeout risk under any meaningful load.&lt;/p&gt;&lt;h3&gt;5. Network issues between Cloudflare and your server&lt;/h3&gt;&lt;p&gt;Sometimes the server itself is healthy. The failure sits in the network path between Cloudflare’s edge and your hosting provider’s data center. &lt;/p&gt;&lt;p&gt;Packet loss, routing errors, or provider-level congestion can all cause intermittent 522 errors in those situations.&lt;/p&gt;&lt;h2&gt;Error 522 vs. Error 521 vs. Error 524&lt;/h2&gt;&lt;p&gt;These three Cloudflare errors describe different failure points in the same connection chain.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Error 521:&lt;/strong&gt; Cloudflare could not connect to your server at all. The server is actively refusing connections, either because it is offline or a firewall rule is explicitly blocking Cloudflare.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Error 522:&lt;/strong&gt; Cloudflare attempted a TCP connection and received no response within the timeout window. The server is not rejecting requests. It is simply not answering them.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Error 524:&lt;/strong&gt; Cloudflare established a connection successfully, but the server took too long to send back an HTTP response. The TCP handshake worked. The server just processed the request too slowly.&lt;/li&gt;
&lt;/ul&gt;&lt;figure&gt;&lt;img src=&quot;https://fluentsupport.com/wp-content/uploads/2026/05/Cloudflare-Error-Comparison_-521-vs-522-vs-524-Fluent-Support-Blog-1024x683.webp&quot; alt=&quot;Cloudflare Error Comparison 521 vs 522 vs 524, Fluent Support Blog, how to fix error 522&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;h2&gt;How to fix Error 522: Step by step&lt;/h2&gt;&lt;h3&gt;1. Confirm your server is actually online&lt;/h3&gt;&lt;p&gt;Start by checking whether the site is down for everyone or just you. Tools like &lt;a href=&quot;https://www.uptrends.com/&quot;&gt;Uptrends&lt;/a&gt; or &lt;a href=&quot;https://downforeveryoneorjustme.com/&quot;&gt;Down for Everyone or Just Me&lt;/a&gt; can confirm this in seconds. Also check &lt;a href=&quot;https://www.cloudflarestatus.com/&quot;&gt;Cloudflare’s status page&lt;/a&gt; to rule out a Cloudflare-side incident before touching anything on your server.&lt;/p&gt;&lt;p&gt;Then try bypassing Cloudflare entirely. Go to your Cloudflare dashboard, open Overview, and under Advanced Actions select Pause Cloudflare on Site. Wait a few minutes, then visit your site directly. &lt;/p&gt;&lt;p&gt;If it loads, your server is running and the issue is in the Cloudflare-to-server path. If the site still does not load, the problem is with your origin server itself.&lt;/p&gt;&lt;h3&gt;2. Check server resource usage&lt;/h3&gt;&lt;p&gt;Log into your hosting control panel and review CPU, RAM, and I/O usage. Servers that are consistently hitting their limits cannot accept new connections fast enough during traffic surges. Common solutions include removing unused plugins, optimizing database queries, enabling a caching layer, or upgrading to a plan with more resources.&lt;/p&gt;&lt;p&gt;[Insert screenshot example of a hosting panel resource usage dashboard showing CPU and RAM charts]&lt;/p&gt;&lt;h3&gt;3. Whitelist Cloudflare IP ranges&lt;/h3&gt;&lt;p&gt;This step resolves the majority of persistent 522 errors. Your firewall must allow all Cloudflare IP addresses on ports 80 and 443 for full TCP traffic, not just the initial handshake packet. Cloudflare publishes its full list of IP ranges at &lt;a href=&quot;https://www.cloudflare.com/ips/&quot;&gt;cloudflare.com/ips&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;For Apache servers, add allow rules to your .htaccess file. For Linux servers with iptables access, add accept rules for each Cloudflare IP range. Most hosting control panels include an IP manager where you can add these ranges directly without touching config files.&lt;/p&gt;&lt;h3&gt;4. Verify your cloudflare DNS a record&lt;/h3&gt;&lt;p&gt;Go to your Cloudflare dashboard, select your domain, and open the DNS section. Check the A record for your root domain. The IP address in the Content field must match the actual current IP address of your origin server.&lt;/p&gt;&lt;p&gt;Find your server’s IP in your hosting panel under plan details or server information. If there is a mismatch, update the A record in Cloudflare. DNS changes can take up to 24 hours to propagate globally, though most resolve within minutes.&lt;/p&gt;&lt;h3&gt;5. Enable KeepAlive on your web server&lt;/h3&gt;&lt;p&gt;For Apache, open your httpd.conf or apache2.conf file and confirm KeepAlive On is set. Set KeepAliveTimeout to at least 75 seconds so connections stay open past Cloudflare’s 90-second resource request window.&lt;/p&gt;&lt;p&gt;For Nginx, check nginx.conf for the keepalive_timeout directive and set it to 75s or higher. Restart your web server after making these changes for them to take effect.&lt;/p&gt;&lt;h3&gt;6. Review server and application logs&lt;/h3&gt;&lt;p&gt;When the steps above do not resolve the error, server logs are the next place to investigate. Check your web server logs (Apache’s error_log, Nginx’s error.log) and your application logs for entries around the time of the error. &lt;/p&gt;&lt;p&gt;A crashing PHP process, a hanging database query, or a misconfigured .htaccess rule can all cause the server to stop responding to new connections without any obvious outward sign.&lt;/p&gt;&lt;h2&gt;Does Error 522 affect SEO?&lt;/h2&gt;&lt;p&gt;Yes. Search engine crawlers treat 5xx responses as server errors. If Googlebot repeatedly encounters error 522 on your site, it will reduce crawl frequency and may eventually remove affected URLs from the index. &lt;/p&gt;&lt;p&gt;Persistent connection timeouts are among the more damaging server-side issues for search visibility. Resolving the root cause quickly and monitoring uptime proactively will protect your rankings.&lt;/p&gt;&lt;h2&gt;How to prevent Error 522 going forward&lt;/h2&gt;&lt;p&gt;Set up uptime monitoring so you learn about downtime before your customers do. Review your Cloudflare DNS records after every server migration or hosting provider change. &lt;/p&gt;&lt;p&gt;Audit firewall rules after major security updates and plugin changes, since some updates silently reset IP whitelist configurations. Watch server resource trends over time. CPU usage sitting above 70% consistently is a signal to scale up before a traffic spike forces the issue.&lt;/p&gt;&lt;p&gt;On the support side, site errors like 522 tend to generate a surge of tickets. Having a structured system in place means your team can triage and respond to those reports without things getting chaotic. &lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://fluentsupport.com/&quot;&gt;Fluent Support&lt;/a&gt; gives WordPress-based support teams a proper workflow to manage technical issue reports at scale. For teams running &lt;a href=&quot;https://fluentsupport.com/ecommerce-customer-service/&quot;&gt;ecommerce support&lt;/a&gt;, where uptime directly affects revenue, that structure matters a lot.&lt;/p&gt;&lt;p&gt;Want a system that makes ticket handling this smooth? Fluent Support brings structure and clarity to every request so your team always knows exactly what to do next. See how it works.&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://fluentsupport.com/features/&quot;&gt;&lt;span&gt;Explore Fluent Support Now!&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;Error 522 is a server-side problem. Cloudflare surfaces it, but your origin server is the source. The fix almost always traces back to one of four things: server overload, Cloudflare IPs blocked by a firewall, a stale DNS A record, or KeepAlive misconfiguration.&lt;/p&gt;&lt;p&gt;Work through the steps in order. Start with server status, then firewall rules, then DNS, then KeepAlive settings. Most 522 errors resolve at step two or three.&lt;/p&gt;&lt;h2&gt;FAQ&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Is error 522 always the server owner’s fault? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In nearly all cases, yes. The error points to your origin server environment. Your hosting provider, firewall configuration, or server resource limits are where the fix lives.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Can a DDoS attack cause error 522? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Yes. Cloudflare absorbs most DDoS traffic at the edge, but an extremely large attack can still overwhelm your origin server and cause it to stop responding to connection requests entirely.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Will pausing Cloudflare fix error 522? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Pausing Cloudflare is a diagnostic step. If your site loads after pausing it, the problem is in the Cloudflare-to-server connection path. You still need to resolve the underlying cause before re-enabling Cloudflare.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Does error 522 affect only WordPress sites? &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;No. Error 522 can occur on any website that routes traffic through Cloudflare, regardless of the CMS or platform. The causes and fixes are the same across setups.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Considering writing with Þorn and Eð characters. Is that annoying to read?</title>
<link>https://slipfast.bearblog.dev/considering-writing-with-thorn-and-eth-characters-is-that-annoying-to-read/</link>
<guid isPermaLink="false">7WYRZvw_iyW2ckqzXd3uej2SqMfIzmQ7ruB5Xg==</guid>
<pubDate>Fri, 15 May 2026 21:20:41 +0000</pubDate>
<description>I’m considering using the þorn &amp; eð characters in my posts, but I do worry that people who aren’t used to seeing them might find their presence annoying or confusing. Like, what it turns away potential readers.</description>
<content:encoded>&lt;p&gt;I’m considering using the þorn &amp;amp; eð characters in my posts, but I do worry that people who aren’t used to seeing them might find their presence annoying or confusing. Like, what it turns away potential readers.&lt;/p&gt;&lt;p&gt;They are very cool characters tho…&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Community building at the edge of the Internet</title>
<link>https://news.dyne.org/the-edge-of-the-internet/</link>
<enclosure type="image/jpeg" length="0" url="https://news.dyne.org/content/images/2026/05/alina-grubnyak-ZiQkhI7417A-unsplash-1.webp"></enclosure>
<guid isPermaLink="false">IUp4CbzMT04DUEVrz4bPGNRzTeTM5B84yVTpug==</guid>
<pubDate>Fri, 15 May 2026 13:37:50 +0000</pubDate>
<description>What if your community could exist without phone numbers, email addresses or third party websites? What if all your community needed to provide digital services to its constituents was a Nostr Relay on the Local Area Network?</description>
<content:encoded>&lt;h2&gt;Why Build a Nostr Community Hub?&lt;/h2&gt;&lt;p&gt;The internet has given us incredible tools for connection, yet most community spaces (Facebook groups, Discord servers, Slack workspaces) operate on a fundamental trade-off: you hand over your identity and data in exchange for access. When you leave the platform, you leave behind your connections, your content, and your history. There are good alternatives, but most of them are only available through the Internet and, if they go down, everything the community has achieved goes with it.&lt;/p&gt;&lt;p&gt;Nostr changes this equation entirely. &lt;a href=&quot;https://news.dyne.org/the-future-was-federated/&quot;&gt;In its paradigm,&lt;/a&gt; your identity is a cryptographic keypair that only you control. Your &lt;code&gt;npub&lt;/code&gt; (public key) is your username, and your &lt;code&gt;nsec&lt;/code&gt; (private key) is your password. There&amp;#39;s no &amp;quot;forgot password&amp;quot; email reset because there&amp;#39;s no central server holding your account. &lt;strong&gt;You can use this identity across dozens of clients, relays, networks, or generate entirely separate identities for different contexts.&lt;/strong&gt; Want one identity for your local bar community and another for professional networking? &lt;em&gt;Easy.&lt;/em&gt; No phone number verification, no email confirmation, no &amp;quot;real name&amp;quot; policy. You and your community define what your identity means.&lt;/p&gt;&lt;p&gt;The problem is that Nostr is very new and thus its pioneers often face a cold-start problem: they generate a key, open a client, connect to a public relay, and are immediately hit with crypto spam and content they didn&amp;#39;t sign up for.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;A community hub solves this elegantly. &lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;A &amp;quot;Pyramid&amp;quot;, but... portable&lt;/h3&gt;&lt;p&gt;Pyramid is the name of a Nostr relay software. It acts as the anchor: a clean, moderated space where your community&amp;#39;s notes, images, and conversations live. &lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://github.com/fiatjaf/pyramid/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;GitHub - fiatjaf/pyramid: a wondrous furnace of communityzenship backed by a dynamic ladder of socialhood&lt;/div&gt;&lt;div&gt;a wondrous furnace of communityzenship backed by a dynamic ladder of socialhood - fiatjaf/pyramid&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/pinned-octocat-093da3e6fa40-25.svg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;GitHub&lt;/span&gt;&lt;span&gt;fiatjaf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/pyramid&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;This is a feature-rich Nostr server that includes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Group Chats&lt;/strong&gt; via &lt;a href=&quot;https://github.com/nostr-protocol/nips/blob/master/29.md?ref=news.dyne.org&quot;&gt;NIP-29&lt;/a&gt; communities&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Collaborative Git repositories via &lt;/strong&gt;&lt;a href=&quot;https://gitgrasp.com/?ref=news.dyne.org&quot;&gt;&lt;strong&gt;GRASP&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;,&lt;/strong&gt; for shared documents or community projects&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Member-only relay access&lt;/strong&gt; with whitelist controls, based on invitation from existing members.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/hzrd149/blossom?ref=news.dyne.org&quot;&gt;&lt;strong&gt;Blossom server&lt;/strong&gt;&lt;/a&gt; for binary file uploads—images, video reels, music, PDFs, all stored on your own infrastructure&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;👀&lt;strong&gt; Behold Dyne.org&amp;#39;s pyramid relay:&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://relay.dyne.org/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;Dyne Nostr&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/favicon-38.ico&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/main.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;div&gt;
            
            &lt;div&gt;
                
                
                    &lt;div&gt;
                    
                        &lt;div&gt;
                            &lt;p&gt;&lt;span&gt;See yourself in the &lt;/span&gt;&lt;a href=&quot;https://relay.dyne.org/?ref=news.dyne.org&quot;&gt;&lt;span&gt;Invite Tree&lt;/span&gt;&lt;/a&gt;&lt;span&gt;? Invite a dyne from the channels! &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;strong&gt;Don&amp;#39;t see yourself in it? Ask a dyne in the channels to invite you!&lt;/strong&gt;&lt;/b&gt;&lt;/p&gt;
                        &lt;/div&gt;
                    
                    
                        &lt;a href=&quot;https://dyne.org/contact/?ref=news.dyne.org&quot;&gt;
                            Ask for an invite! 👋
                        &lt;/a&gt;
                        
                    &lt;/div&gt;
                
            &lt;/div&gt;
        &lt;/div&gt;&lt;h3&gt;Rumble in the Jumble, community mode&lt;/h3&gt;&lt;p&gt;&amp;quot;Jumble&amp;quot; is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Progressive_web_app?ref=news.dyne.org&quot;&gt;Progressive Web App&lt;/a&gt; (PWA) that runs a Nostr client. &lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://github.com/CodyTseng/jumble?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;GitHub - CodyTseng/jumble: A user-friendly Nostr client for exploring relay feeds&lt;/div&gt;&lt;div&gt;A user-friendly Nostr client for exploring relay feeds - CodyTseng/jumble&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/pinned-octocat-093da3e6fa40-26.svg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;GitHub&lt;/span&gt;&lt;span&gt;CodyTseng&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/54db5a2f-be21-4236-b550-0e67f15b00dd&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;It is shipped with a so-called &amp;quot;Community Mode&amp;quot;, which lets you predefine a set of default relays for the people leveraging your build of the PWA. Basically, when someone visits your custom Jumble instance (i.e &lt;code&gt;jumble.your-community-domain.tld&lt;/code&gt;), &lt;strong&gt;they&amp;#39;re immediately connected to &lt;em&gt;your&lt;/em&gt; curated relay ecosystem.&lt;/strong&gt; Not the global firehose.&lt;/p&gt;&lt;p&gt;🪇&lt;strong&gt; Here is dyne.org&amp;#39;s community client:&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://nostr.dyne.org/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;Jumble Ouro&lt;/div&gt;&lt;div&gt;Community-governed fork of Jumble — a Nostr client whose features are voted on and shipped by an AI coding agent.&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/favicon-13.svg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;Jumble Ouro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/og-image-1.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;hr/&gt;&lt;h2&gt;A cosy digital place&lt;/h2&gt;&lt;h3&gt;It&amp;#39;s cheap&lt;/h3&gt;&lt;p&gt;This setup can run on very scarce resources and can easily migrate. It can even be decommissioned with near-zero collateral costs for the people using it: the people never depend on a relay. A person or a bot having to change their Nostr in/out-boxes is the worst-case scenario.&lt;/p&gt;&lt;h3&gt;The escape hatch is always open&lt;/h3&gt;&lt;p&gt;Once they get the gist of it, people can start exploring other clients, rather than your community&amp;#39;s build. Maybe they collaborate on another GRASP (&lt;a href=&quot;https://gitgrasp.com/?ref=news.dyne.org&quot;&gt;Git over Nostr&lt;/a&gt;)? Maybe they want a client for &lt;a href=&quot;https://zap.stream/?ref=news.dyne.org&quot;&gt;live-streaming&lt;/a&gt;? Maybe they want to add the relay accessible in their &lt;strong&gt;LBAN (Local Bar&amp;#39;s Area Network)&lt;/strong&gt; in the settings of the client of their choice, because they want to vote for the next Karaoke track? They can always explore communities beyond, with the identity of their choice. Your community hub is just providing a warm, welcoming starting point for a journey through cyberspace. Not an enshitifyable cage. &lt;/p&gt;&lt;h3&gt;This setup is ideal for:&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Local bars and cafes&lt;/strong&gt; that want a digital bulletin board and social space for regulars. &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Neighborhood communities&lt;/strong&gt; sharing events, recommendations, and lost cat notices. &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Interest groups&lt;/strong&gt; (book clubs, running crews, gardening collectives) that want a private-ish space without building a whole Discord server&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Small businesses&lt;/strong&gt; looking for a lightweight customer community platform and an easy way to update their website through an app.&lt;/li&gt;&lt;/ul&gt;&lt;hr/&gt;&lt;h2&gt;How do I use it?&lt;/h2&gt;&lt;p&gt;You need an &lt;strong&gt;identity.&lt;/strong&gt; One identity is two things: &lt;/p&gt;&lt;ol&gt;&lt;li&gt;An &lt;code&gt;npub&lt;/code&gt;. This is a unique public identifier of your &lt;strong&gt;identity.&lt;/strong&gt; &lt;/li&gt;&lt;li&gt;An &lt;code&gt;nsec&lt;/code&gt;. This is the secret key. The one that controls it all and must stay secret for as long as it takes. &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If you are starting from scratch, create an &lt;strong&gt;identity&lt;/strong&gt; using an &lt;em&gt;&lt;strong&gt;app&lt;/strong&gt;&lt;/em&gt;. No internet needed. This is confusing at first, and there are many new words in the authentication flow: &lt;em&gt;&amp;quot;remote signer&amp;quot;, &amp;quot;authenticator&amp;quot;, &amp;quot;bunker&amp;quot;, &amp;quot;passkey&amp;quot;&lt;/em&gt;... Essentially, the &lt;strong&gt;&lt;em&gt;app&lt;/em&gt;&lt;/strong&gt; is the tool that allows you to &amp;quot;log in to your account&amp;quot;. It leverages some cool Planet Dyne adjacent technology from Lugano, code-named NIP-46&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://nostr-nips.com/nip-46?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;Nostr | NIPs&lt;/div&gt;&lt;div&gt;Documentation site for NIPs&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/nip-46&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;Nextra&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;Super 1337 users will want to look at the CLI tool: &lt;code&gt;nak&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# generate a secret key

~&amp;gt; nak key generate
1e90fa2e277921992920dfc4fc9126896e9a3af8ee0fa57fe8dbbf302ac90e68

# generate a public key from the secret one
~&amp;gt; nak key public 1e90fa2e277921992920dfc4fc9126896e9a3af8ee0fa57fe8dbbf302ac90e68
6cf7e488d65abbebd032336101c43d1f2e2a93ccc97856388a28f79e017f9336&lt;/code&gt;&lt;/pre&gt;&lt;figure&gt;&lt;a href=&quot;https://github.com/fiatjaf/nak?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;GitHub - fiatjaf/nak: a command line tool for doing all things nostr&lt;/div&gt;&lt;div&gt;a command line tool for doing all things nostr. Contribute to fiatjaf/nak development by creating an account on GitHub.&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/pinned-octocat-093da3e6fa40-28.svg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;GitHub&lt;/span&gt;&lt;span&gt;fiatjaf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/nak&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;At the time of writing, it&amp;#39;s unclear if such an app exists on iOS, but on Android, there&amp;#39;s this one:&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://github.com/greenart7c3/Amber?ref=news.dyne.org#amber-nostr-event-signer-for-android&quot;&gt;&lt;div&gt;&lt;div&gt;GitHub - greenart7c3/Amber&lt;/div&gt;&lt;div&gt;Contribute to greenart7c3/Amber development by creating an account on GitHub.&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/pinned-octocat-093da3e6fa40-27.svg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;GitHub&lt;/span&gt;&lt;span&gt;greenart7c3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/Amber&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;Then, to log in, you can point at the QR from a web app and log in. &lt;/p&gt;&lt;p&gt;You can also create and manage identities using extensions for your browser:&lt;/p&gt;&lt;p&gt;Firefox&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/nos2x-fox/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;nos2x-fox – Get this Extension for 🦊 Firefox (en-US)&lt;/div&gt;&lt;div&gt;Download nos2x-fox for Firefox. Nostr Signer Extension (for Firefox)&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/favicon-42.ico&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;Diego&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/287276.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;Chrome&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://chromewebstore.google.com/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;nos2x - Chrome Web Store&lt;/div&gt;&lt;div&gt;Nostr Signer Extension&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/icon_144px.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;Chrome Web Store&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/8mm7IxpiAOulzNPyyvILMAWHuRI6VqYpfIpDYU094vTGy6XOlTPswAEj-bQ325E8-RpSo-Sbhs9umF1drHC5Od7UpA-s128-rj-sc0x00ffffff&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;h3&gt;But let&amp;#39;s talk about &lt;a href=&quot;https://relay.dyne.org/?ref=news.dyne.org&quot;&gt;wss://relay.dyne.org&lt;/a&gt; for a moment! &lt;/h3&gt;&lt;p&gt;The dyne.org community can use &lt;code&gt;wss://relay.dyne.org&lt;/code&gt; as &lt;strong&gt;outbox&lt;/strong&gt; (write) to save a profile, write notes, share photos and videos, collaborate on Git repositories, and chat in groups. All while everyone maintains full control over their own identity. For &lt;strong&gt;inbox&lt;/strong&gt; (read), thats is incomming stuff, like DMs, replies, reactions to one&amp;#39;s content, zaps, etc...) the community can use &lt;code&gt;wss://relay.dyne.org/inbox&lt;/code&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You can see your relay settings here: &lt;/strong&gt;&lt;a href=&quot;https://nostr.dyne.org/settings/relays?ref=news.dyne.org&quot;&gt;&lt;strong&gt;https://nostr.dyne.org/settings/relays&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The Pyramid &lt;a href=&quot;https://relay.dyne.org/?ref=news.dyne.org&quot;&gt;website&lt;/a&gt; is useful in some ways. For example, you can set up your &lt;a href=&quot;https://github.com/nostr-protocol/nips/blob/master/05.md?ref=news.dyne.org&quot;&gt;NIP-05&lt;/a&gt;. This is just a funky way to write your &lt;code&gt;npub&lt;/code&gt;. Say your nick is &lt;code&gt;belli&lt;/code&gt;, now people can find your &lt;code&gt;npub&lt;/code&gt; by searching for &lt;code&gt;belli@relay.dyne.org&lt;/code&gt; . &lt;/p&gt;&lt;p&gt;You&amp;#39;ll find those settings by clicking your username in the upper-right corner when you are logged in to the relay interface here:&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://relay.dyne.org/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;Dyne Nostr&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/favicon-39.ico&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/main-1.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;On the Pyramid web UI, you will also find that there is a GRASP server. Read more about GRASP here:&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://gitgrasp.com/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;grasp: a simple protocol for decentralized git&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/favicon-41.ico&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;Nomadic Group Chats anyone? This client is cool for that:&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://nostrord.com/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;Nostrord - Decentralized Group Chat · NIP-29 Nostr Client&lt;/div&gt;&lt;div&gt;Nostrord is a NIP-29 client for decentralized group chat on Nostr. Built with Kotlin Multiplatform (KMP) and Compose. Relay-enforced moderation, censorship-resistant communities, cryptographic identity. No servers, no sign-up, no lock-in.&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/apple-touch-icon-24.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;The Nostrord Team&lt;/span&gt;&lt;span&gt;The Nostrord Team&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/nostrord-app.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;And a blossom server to store your pictures and videos! You can store up to 256Mb of goodness on there. Blossom is a wonderful concept for portable binary data. Read&lt;a href=&quot;https://github.com/hzrd149/blossom?ref=news.dyne.org&quot;&gt; about it here&lt;/a&gt;, or just try out a client that manages your blossoms here:&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://bouquet.slidestr.net/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;bouquet&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/bouquet.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;h3&gt;Learn more!&lt;/h3&gt;&lt;p&gt;The UX of nostr is wild; there&amp;#39;s an increase in interest among builders, fueling all sorts of client development. Rumble in the Jungle, essentially! If you tested the &lt;a href=&quot;https://nostr.dyne.org/?ref=news.dyne.org&quot;&gt;Dyne.org community build of Jumble&lt;/a&gt;, you might have noticed that one relay in the network is &lt;a href=&quot;https://spatia-arcana.com/?ref=news.dyne.org&quot;&gt;Spatia-arcana.com&lt;/a&gt;. They&amp;#39;ve written extensively to help onboard new people. I urge you to consult the manual they&amp;#39;re developing here:&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://spatianostra.com/the-pyramid-community-relay/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;The Pyramid Community Relay&lt;/div&gt;&lt;div&gt;* Getting Started with Pyramid * Pyramid’s Features, pt 1 (Home, Main, and Subrelays) * Pyramid’s Features, pt 2 (Media, Chats, and More) * Managing Your Pyramid Community * Benefits of Pyramid Membership * Troubleshooting Your Pyramid&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/spatianostra1-3-1.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;Spatia Nostra&lt;/span&gt;&lt;span&gt;D.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/491328156_1826538618205061_4065938803586195695_n-1.jpg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;Here&amp;#39;s some very useful high-level information about Nostr in general.&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://nostr.com/?ref=news.dyne.org&quot;&gt;&lt;div&gt;&lt;div&gt;Nostr - Notes and Other Stuff Transmitted by Relays&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/favicon-40.ico&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;Notes and Other Stuff Transmitted by Relays&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/nostr-robot-1.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Nostr is more than a social protocol; it&amp;#39;s a decentralized form of Digital Identity, a topic dear and close to the heart of Planet Dyne.&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://news.dyne.org/privacy-in-eudi/&quot;&gt;&lt;div&gt;&lt;div&gt;Privacy in EUDI&lt;/div&gt;&lt;div&gt;Until process isolation is granted for every execution of zero-knowledge algorithms, privacy-preserving technology won’t protect us from mega-corporations spying on us.&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/icon/logo-2023-12-22-10-24-2-37.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;span&gt;News From Dyne&lt;/span&gt;&lt;span&gt;Jaromil&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;img src=&quot;https://news.dyne.org/content/images/thumbnail/Copy-of-Cyber-Security-1-3.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;h2&gt;..so many questions.&lt;/h2&gt;&lt;p&gt;That&amp;#39;s ok. We can&amp;#39;t answer them all today. But &lt;strong&gt;feel free to drop a comment below or come say hi in the channels! We&amp;#39;ll figure it out together!&lt;/strong&gt;&lt;/p&gt;&lt;h4&gt;Tune in to the discussion &lt;strong&gt;💬&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;(These services are bridged: join your favorite and reach them all)&lt;/p&gt;&lt;p&gt;🗨️ &lt;a href=&quot;https://irc.dyne.org/?ref=news.dyne.org&quot;&gt;&lt;strong&gt;IRC&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;🗨️ &lt;a href=&quot;https://irc.dyne.org/?ref=news.dyne.org&quot;&gt;&lt;strong&gt;Matrix&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;🗨️ &lt;a href=&quot;https://socials.dyne.org/telegram?ref=news.dyne.org&quot;&gt;&lt;strong&gt;Telegram&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;🗨️ &lt;a href=&quot;https://socials.dyne.org/discord?ref=news.dyne.org&quot;&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://news.dyne.org/content/images/2024/11/support.svg&quot; alt=&quot;Support Dyne 🫱🏿‍🫲🏾&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;p&gt;Help dyne.org stay focused on hacking the planet!&lt;/p&gt;&lt;div&gt;&lt;a href=&quot;https://news.dyne.org/#/portal/support&quot;&gt;🥰 Donate 💸&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;🪙 Bitcoins: &lt;/strong&gt;&lt;code&gt;bc1qz9wz2f9swcefra2tfrhk4fx49evqsv03m9nx4l&lt;/code&gt;&lt;br/&gt;&lt;strong&gt;☕ &lt;/strong&gt;&lt;a href=&quot;https://ko-fi.com/dyneorg?ref=news.dyne.org#&quot;&gt;&lt;strong&gt;Ko-Fi&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;&lt;strong&gt;🍴 &lt;/strong&gt;&lt;a href=&quot;https://github.com/sponsors/dyne?ref=news.dyne.org&quot;&gt;&lt;strong&gt;Github.com&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;&lt;strong&gt;🧁 &lt;/strong&gt;&lt;a href=&quot;https://liberapay.com/dyne/?ref=news.dyne.org&quot;&gt;&lt;strong&gt;LiberaPay&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;&lt;strong&gt;🍥 &lt;/strong&gt;&lt;a href=&quot;https://www.patreon.com/dyneorg?ref=news.dyne.org&quot;&gt;&lt;strong&gt;Patreon.com&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;h4&gt;Follow &lt;a href=&quot;https://dyne.org/?ref=news.dyne.org&quot;&gt;Dyne.org&lt;/a&gt; 🗞️&lt;/h4&gt;&lt;p&gt;Social Media everywhere!&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Two-Minute _Iolanthe_</title>
<link>https://quuxplusone.github.io/blog/2026/05/08/two-minute-iolanthe/</link>
<guid isPermaLink="false">YCLneo2VQYdnPkb5zMo1J0XASGBhVSEBNFH04A==</guid>
<pubDate>Fri, 15 May 2026 06:55:38 +0000</pubDate>
<description>The other day I came across Connie Kleinjans’ page of “two-minute versions” of G&amp;S shows. She’s got two versions of Gondoliers and one each of Iolanthe and Ruddigore. The technique is the same as in blackout poetry: take the whole work and black out all but the most important and/or funniest bits.</description>
<content:encoded>&lt;p&gt;The other day I came across Connie Kleinjans’ &lt;a href=&quot;https://www.misosoup.com/connie/TwoMin/TwoMin.html&quot;&gt;page of “two-minute versions”&lt;/a&gt;
of G&amp;amp;S shows. She’s got two versions of &lt;em&gt;Gondoliers&lt;/em&gt; and one each of &lt;em&gt;Iolanthe&lt;/em&gt; and &lt;em&gt;Ruddigore&lt;/em&gt;.
The technique is the same as in &lt;a href=&quot;https://en.wikipedia.org/wiki/Blackout_poetry&quot;&gt;blackout poetry&lt;/a&gt;:
take the whole work and black out all but the most important and/or funniest bits.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://quuxplusone.github.io/blog/images/2026-05-08-iolanthe-blackout.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Kleinjans’ short scripts are funny in their own rights, but I wanted audio versions; so I made one.
Presenting &lt;a href=&quot;https://www.youtube.com/watch?v=lhXP-kBlt5k&quot;&gt;“Two-Minute &lt;em&gt;Iolanthe&lt;/em&gt; in five minutes”&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://www.youtube.com/watch?v=DLYSZN2IqeU&quot;&gt;original recording&lt;/a&gt; I “blacked out” for this
video is a TV broadcast of a 1976 production at the Sydney Opera House featuring Rosemary Gunn
(Iolanthe), Heather Begg (Fairy Queen), Dennis Olsen (the Lord Chancellor), June Bronhill (Phyllis),
Lyndon Terracini (Strephon), Graeme Ewer (Mountararat), Ronald Maconaghie (Tolloller), and
Alan Light (Private Willis). This is a low-quality VHS rip of an excellent performance.&lt;/p&gt;&lt;p&gt;The VHS rip on YouTube is missing a chunk of the finale, which
prompted some alterations to the script; I made a few other alterations for pacing. Even after
those cuts, this “two-minute” &lt;em&gt;Iolanthe&lt;/em&gt; is almost five minutes long; watch at 2.3x speed for
a true two-minute experience.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;To create the video, I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; to clip and concat the snippets. When concatenating 169 tiny snippets,
your biggest problem will be “timestamp drift” between the audio and video channels. I spent a long
time cajoling ChatGPT into giving me new permutations of command-line switches to try before finally
settling on the programs linked below.&lt;/p&gt;&lt;p&gt;Step 1 was to get the original video (2.4 gigabytes, saved as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input.mkv&lt;/code&gt;):&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;brew install ffmpeg yt-dlp
yt-dlp &amp;#39;https://www.youtube.com/watch?v=DLYSZN2IqeU&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Step 2 was to snip the constituent bits via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; commands. I factored out the common arguments
into environment variables so I didn’t have to keep typing or tabbing over them.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;PREFIX=&amp;quot;-y&amp;quot;
SUFFIX=&amp;quot;-i input.mkv -c:v libx264 -preset veryfast -crf 20 -c:a aac&amp;quot;
ffmpeg $PREFIX -ss 00:07:07.2 -to 00:07:10.5 $SUFFIX part001.mkv
ffmpeg $PREFIX -ss 00:07:45.0 -to 00:07:48.6 $SUFFIX part002.mkv
~~~~&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; turns out to be supremely sensitive to whether you put the input
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-i input.mkv&lt;/code&gt;) before, or after, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-ss&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-to&lt;/code&gt; switches. With &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-i input.mkv&lt;/code&gt; as
part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SUFFIX&lt;/code&gt;, my whole script.txt runs in 61 seconds; as part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PREFIX&lt;/code&gt;,
it takes 98 &lt;em&gt;minutes.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;To concatenate all those clips and re-encode a “preview” video, we can do this:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;rm list.txt
for i in part*.mkv ; do echo &amp;quot;file $i&amp;quot; &amp;gt;&amp;gt;list.txt ; done
ffmpeg -y -f concat -safe 0 -i list.txt \
  -c:v libx264 -crf 20 -preset veryfast -c:a aac -ar 48000 output.mp4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Step 3 was to fight timestamp desynchronization. My solution here was generated entirely
by blind fumbling and incantations, with input from ChatGPT. It seems that there are
basically two ways to get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; to “supercut” a video as we’re doing here: either
clip out the clips into temporary files and then concatenate all those little files
(as we did above — this way causes a lot of drift), or do one big “filter” operation
to take just the frames you care about in a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; invocation. That looks like
this, except with 169 clips instead of 3:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;ffmpeg -y -i input.mkv -filter_complex \
 &amp;quot;[0:v]trim=start=427.2:end=430.5,setpts=PTS-STARTPTS[v0];
  [0:a]atrim=start=427.2:end=430.5,asetpts=PTS-STARTPTS[a0];
  [0:v]trim=start=465.0:end=468.6,setpts=PTS-STARTPTS[v1];
  [0:a]atrim=start=465.0:end=468.6,asetpts=PTS-STARTPTS[a1];
  [0:v]trim=start=616.5:end=618.7,setpts=PTS-STARTPTS[v2];
  [0:a]atrim=start=616.5:end=618.7,asetpts=PTS-STARTPTS[a2];
  [v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[v][a]&amp;quot;
  -map [v] -map [a] \
  -c:v libx264 -crf 20 -preset veryfast \
  -c:a aac -b:a 192k -ar 48000 \
  output.mp4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;That’s horribly slow; it seems to have quadratic behavior as the number of clips
increases. Even worse is trying to use the non-“&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;complex&lt;/code&gt;” filter options &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-vf&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-af&lt;/code&gt;:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;ffmpeg -y -i input.mkv \
  -vf &amp;quot;select=between(t,427.2,430.5)+between(t,465.0,468.6)+between(t,616.5,618.7),setpts=N/FRAME_RATE/TB&amp;quot; \
  -af &amp;quot;aselect=between(t,427.2,430.5)+between(t,465.0,468.6)+between(t,616.5,618.7),asetpts=N/SR/TB&amp;quot; \
  -c:v libx264 -crf 20 -preset veryfast \
  -c:a aac -b:a 192k -ar 48000 \
  output.mp4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;That just makes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; run out of memory before you’ve even hit 50 clips.
So I ended up using a hybrid approach: I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-filter_complex&lt;/code&gt; to produce
nine intermediate concatenations of 20 clips at a time, and then used&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;ffmpeg -y -f concat -i list.txt -c copy output.mp4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;to paste those nine files together. I’ve saved my programs for posterity:
&lt;a href=&quot;https://quuxplusone.github.io/blog/code/2026-05-08-iolanthe-script.txt&quot;&gt;script.txt&lt;/a&gt; runs as a Bash script
(in just over 1 minute on my machine) to create that “draft preview” video output;
its textual contents also serve as input to &lt;a href=&quot;https://quuxplusone.github.io/blog/code/2026-05-08-iolanthe-script.py&quot;&gt;script.py&lt;/a&gt;,
which creates the final product (in about 9 minutes) using the two-level &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-filter_complex&lt;/code&gt;
approach. The finished &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.mp4&lt;/code&gt; is about 42 megabytes in size.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Amazonbot is finally respecting robots.txt - Xe Iaso</title>
<link>https://xeiaso.net/notes/2026/amazonbot-respecting-robots-txt/</link>
<guid isPermaLink="false">TTk5OAJYpK-UtbVD7_Qp_Oa3uzGrwXzSTDIrKg==</guid>
<pubDate>Fri, 15 May 2026 04:34:22 +0000</pubDate>
<description>Thanks for giving me a viable business model Amazon!</description>
<content:encoded>&lt;body&gt;
        &lt;header&gt;
            
        &lt;/header&gt;

        

        

        &lt;div&gt;
            &lt;article&gt;
    
    &lt;h1&gt;Amazonbot is finally respecting robots.txt&lt;/h1&gt;

    &lt;div&gt;
        &lt;div&gt;
            &lt;p&gt;
                Published on &lt;time&gt;2026-05-14&lt;/time&gt;, 239 words, 1 minutes to read
            &lt;/p&gt;

            
                &lt;p&gt;Thanks for giving me a viable business model Amazon!&lt;/p&gt;
            
        &lt;/div&gt;
        &lt;div&gt;
            
        &lt;/div&gt;
    &lt;/div&gt;

    
        
            
    

    

        
    

    

    &lt;p&gt;I just got an email from Amazon saying they&amp;#39;re finally going to respect &lt;a href=&quot;https://www.robotstxt.org/&quot;&gt;robots.txt&lt;/a&gt;. Here&amp;#39;s the verbatim email I got:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We are writing to inform you that starting Monday, June 15, 2026, crawl preferences for Amazonbot will be managed solely through the industry-standard directives. This gives you direct, ongoing control over how Amazonbot accesses your site, rather than relying on manual requests. If you do not implement robots.txt directives by that date, Amazonbot will follow standard web crawling practices when accessing your site.&lt;/p&gt;
&lt;p&gt;How to maintain your current preferences: The robots.txt protocol allows you to control Amazonbot’saccess at the page-, directory-, or site-level and update your preferences at any time. Please find detailed information on Amazonbots approach to these directives here: &lt;a href=&quot;https://developer.amazon.com/amazonbot&quot;&gt;https://developer.amazon.com/amazonbot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Best,&lt;/p&gt;
&lt;p&gt;Amazon Publisher Support&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;mailto:amazonbot@amazon.com&quot;&gt;amazonbot@amazon.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Get Outlook for Mac&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;div&gt;&lt;img src=&quot;https://stickers.xeiaso.net/sticker/numa/smug&quot; alt=&quot;Numa is smug&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;a href=&quot;https://xeiaso.net/characters#numa&quot;&gt;Numa&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;p&gt;Amazing, they even kept the &amp;quot;sent from my iPhone&amp;quot; message proving they sent it
from Outlook for Mac. Looking at the email headers it has a bunch of
Exchange-specific headers so it&amp;#39;s probably actually from Outlook for Mac. This
timeline is absolutely wild.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This makes me feel kinda weird because &lt;a href=&quot;https://xeiaso.net/talks/2025/surreal-joy-homelab/&quot;&gt;Amazon&amp;#39;s scraper is why Anubis exists&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m gonna make sure to merge these robots.txt changes into Anubis if they aren&amp;#39;t already there.&lt;/p&gt;

    &lt;hr/&gt;

    

    

    &lt;p&gt;Facts and circumstances may have changed since publication. Please contact me before jumping to conclusions if something seems wrong or unclear.&lt;/p&gt;

    &lt;p&gt;Tags: &lt;/p&gt;
&lt;/article&gt;
        &lt;/div&gt;

        &lt;footer&gt;
            &lt;div&gt;
                &lt;p&gt;Copyright 2012-2026 Xe Iaso. Any and all opinions listed here are my own and
                not representative of any of my employers, past, future, and/or present.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div&gt;
                &lt;p&gt;Like what you see? Donate on &lt;a href=&quot;https://patreon.com/cadey&quot;&gt;Patreon&lt;/a&gt; like &lt;a href=&quot;https://xeiaso.net/patrons&quot;&gt;these awesome people&lt;/a&gt;!&lt;/p&gt;
            &lt;/div&gt;
            &lt;div&gt;
                &lt;p&gt;Served by xesite v4 (/app/bin/xesite) with site version 
                        &lt;a href=&quot;https://github.com/Xe/site/commit/28687b430d93ba5c0eba9cd84c59cd16074b588f&quot;&gt;28687b43&lt;/a&gt;
                    , source code available &lt;a href=&quot;https://github.com/Xe/site&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/footer&gt;
        
    &lt;/body&gt;</content:encoded>
</item>
<item>
<title>Browsers Treat Big Sites Differently — Den Odell</title>
<link>https://denodell.com/blog/browsers-treat-big-sites-differently</link>
<enclosure type="image/jpeg" length="0" url="https://denodell.com/_astro/browsers-treat-big-sites-differently.Db_-AE3U_Z2fX3pi.jpg"></enclosure>
<guid isPermaLink="false">JHsYMwQe08X3DF39ezevYChLdWYZktpDToIPWg==</guid>
<pubDate>Thu, 14 May 2026 18:05:10 +0000</pubDate>
<description>Safari and Firefox change how big sites render based on the domain. TikTok, Netflix, Instagram… even SeatGuru. Chrome doesn’t. Why is that?</description>
<content:encoded>&lt;p&gt;Some browsers ship code that checks which domain you’re visiting and changes how the page renders based on it.&lt;/p&gt;&lt;p&gt;Yup, you read that right. If site == &lt;em&gt;X&lt;/em&gt;, do &lt;em&gt;Y&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;TikTok gets special treatment. So does Netflix. So does Instagram.&lt;br/&gt;
And so does SeatGuru.&lt;/p&gt;&lt;p&gt;Safari and Firefox both do this. Chrome doesn’t.&lt;br/&gt;
&lt;em&gt;That tells us something interesting.&lt;/em&gt;&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;The &lt;a href=&quot;https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/Quirks.cpp&quot;&gt;source code&lt;/a&gt; is &lt;a href=&quot;https://searchfox.org/firefox-main/source/browser/extensions/webcompat/data/interventions&quot;&gt;right there&lt;/a&gt; if you want to look. These are literal domain checks baked into browser rendering engines that say things like “if the user is on this domain, render this differently” or “if they’re on that domain, handle that API call differently.” It’s not a bug. It’s a feature, and it ships to billions of devices.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;Firefox’s about:compat&lt;/h2&gt;&lt;p&gt;If you open Firefox and type &lt;a href=&quot;https://www.ghacks.net/2019/02/28/firefoxs-new-web-compatibility-page/&quot;&gt;&lt;code&gt;about:compat&lt;/code&gt;&lt;/a&gt; in the address bar, you’ll see a list of site-specific interventions complete with toggle switches. Each one is a targeted fix for a specific website, and you can turn them off and watch sites break.&lt;/p&gt;&lt;img src=&quot;https://denodell.com/_astro/firefox-about-compat.DjihvUrL_Z1p62NP.webp&quot; alt=&quot;Screenshot of Firefox’s about:compat page showing site-specific compatibility interventions.&quot; title=&quot;&quot;/&gt;&lt;p&gt;Firefox’s &lt;a href=&quot;https://github.com/mozilla-firefox/firefox/tree/main/browser/extensions/webcompat&quot;&gt;WebCompat system&lt;/a&gt; injects custom CSS and JavaScript into specific domains, changes user agent strings for sites that sniff browsers incorrectly, and papers over bugs that would otherwise make the web feel broken. The interventions are tracked in Mozilla’s Bugzilla, complete with bug reports and sometimes failed outreach attempts to the sites in question.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;Safari’s “quirks”&lt;/h2&gt;&lt;p&gt;Safari’s WebKit engine calls them “quirks,” and the file &lt;a href=&quot;https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/Quirks.cpp&quot;&gt;&lt;code&gt;Quirks.cpp&lt;/code&gt;&lt;/a&gt; is publicly available on GitHub. Reading through it is an education in how the web actually works. Here’s one comment from the code:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Facebook, X (twitter), and Reddit will naively pause a &amp;lt;video&amp;gt; element that has scrolled out of the viewport, regardless of whether that element is currently in PiP mode.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;So the browser detects when you’re on facebook.com, x.com, or reddit.com and changes how it handles Picture-in-Picture video. These companies wrote broken video code, and rather than wait for them to fix it, the browser shipped a workaround to every user. Here’s another comment:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;FIXME: Remove this quirk if seatguru decides to adjust their site.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Someone added domain-specific rendering code for SeatGuru, and the comment implies outreach was attempted. SeatGuru didn’t fix their site, so the browser fixed it for them.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://github.com/WebKit/WebKit/commits/main/Source/WebCore/page/Quirks.cpp&quot;&gt;commit history&lt;/a&gt; is a fascinating read. In just the last few months: Zillow’s floorplan images weren’t centering, TikTok was showing “please upgrade your browser” messages, Instagram Reels were resizing erratically during playback, Netflix’s “Episodes and Info” button was dismissing popovers incorrectly, Twitch was pausing PiP videos when you switched tabs, and Amazon Prime Video wasn’t letting Safari users watch at all. Each one got a domain-specific fix shipped to every single user.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;Chrome Is Different&lt;/h2&gt;&lt;p&gt;The quirks files aren’t just fixing broken sites; they’re often compensating for Chrome’s control over what “working” means in the first place.&lt;/p&gt;&lt;p&gt;The pattern goes like this: Chrome ships a feature, developers use it because Chrome dominates the market, and other browsers scramble to either implement the feature or add site-specific quirks to paper over the difference. By the time Safari or Firefox catches up, the quirk has already shipped to millions of users.&lt;/p&gt;&lt;p&gt;WebKit’s source code includes user agent overrides that make Safari pretend to be Chrome for specific sites like Amazon’s video pages and various streaming services. These sites sniff for Chrome and serve degraded experiences to everyone else, so rather than let Safari users suffer, WebKit lies about what browser it is. From the current Quirks.cpp source:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;auto chromeUserAgent = &amp;quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36&amp;quot;_s;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Safari literally ships with a fake Chrome user agent string, ready to deploy when sites refuse to work otherwise. Firefox does the same thing, and many of its &lt;code&gt;about:compat&lt;/code&gt; interventions are user agent spoofs telling sites “yes, I’m Chrome” because those sites actively block or break on non-Chrome browsers. The &lt;a href=&quot;https://wiki.mozilla.org/Compatibility/UA_Override_&amp;amp;_Interventions_Testing&quot;&gt;Mozilla wiki&lt;/a&gt; explains that some sites “block access completely, display a different design, or provide different functionality” based on browser detection. So Firefox ships workarounds.&lt;/p&gt;&lt;p&gt;This creates a feedback loop. Developers build for Chrome because Chrome dominates. Their sites work best in Chrome. Users who hit bugs elsewhere blame the browser, not the site, so they switch to Chrome, reinforcing its dominance.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;It Goes Deep&lt;/h2&gt;&lt;p&gt;These aren’t just cosmetic tweaks. Browsers change fundamental behavior based on your domain, including scrolling behavior, touch event handling, viewport calculations, and image MIME type handling. The list in WebKit alone runs to thousands of lines.&lt;/p&gt;&lt;p&gt;Here’s one about simulated mouse events:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;When panning on an Amazon product image, we’re either touching on the #magnifierLens element or its previous sibling.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The browser checks if you’re on Amazon and changes how touch-to-mouse event translation works for their product zoom feature. Amazon’s site assumes certain event behavior that Safari doesn’t provide by default, so Safari provides it anyway, but only for Amazon.&lt;/p&gt;&lt;p&gt;There are quirks for storage access, scrollbar rendering, autocorrection behavior, and zoom handling. Each one is behind a domain check, and each one is compiled into the browser executable.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;Chrome Doesn’t Need a Quirks File&lt;/h2&gt;&lt;p&gt;You might have noticed something. I’ve shown you Firefox’s &lt;code&gt;about:compat&lt;/code&gt; and WebKit’s &lt;code&gt;Quirks.cpp&lt;/code&gt;, but where’s Chrome’s equivalent?&lt;/p&gt;&lt;p&gt;Chrome doesn’t really need one, and not necessarily because Chrome is better engineered. The web is already built for Chrome. When &lt;a href=&quot;https://techjury.net/industry-analysis/chrome-browser-market-share/&quot;&gt;over 80% of users browse with Chromium-based browsers&lt;/a&gt;, developers build for Chrome first. If a site works in Chrome, it ships. If it breaks in Safari or Firefox, they decide, knowingly or otherwise, that it’s less of a problem.&lt;/p&gt;&lt;p&gt;Chrome doesn’t add quirks; it sets the agenda. When Chrome changes how something works, sites update to match, and other browsers follow or break.&lt;/p&gt;&lt;p&gt;This is the asymmetry that runs through the modern web. When a site breaks in Safari, WebKit engineers add a quirk. When Chrome wants to change how the web works, Chrome just changes it and everyone else adapts. Chrome doesn’t need quirks because Chrome’s interpretation of web standards is the version that everyone else works to.&lt;/p&gt;&lt;p&gt;This isn’t done maliciously and it isn’t entirely Google’s fault; really it’s the natural consequence of market dominance. Browser engineers will tell you the specs themselves are actually well-defined now. The HTML5 “living specification” approach solved the chaos of the IE/Netscape era by making specs match reality. The problem is that developers rely on unspecified implementation details, then blame non-compliant browsers when those details differ.&lt;/p&gt;&lt;p&gt;While that may be true, it doesn’t change the outcome. When Chrome is the implementation everyone targets, Chrome’s unspecified details become the de facto spec. The same thing happened with Internet Explorer in the 2000s. When developers built for IE, sites broke elsewhere, and standards compliance became secondary to just making it “work in IE.” We spent years digging out of that hole.&lt;/p&gt;&lt;p&gt;A decade ago, the hope was that browser quirks would eventually disappear as the web became more standards-compliant. You could argue they did, but not for the reason anyone expected: the quirks didn’t go away, they just moved to browsers that aren’t Chrome.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;Easier To Fix Than To Wait&lt;/h2&gt;&lt;p&gt;You might wonder why browser vendors don’t just contact the offending sites and ask them to fix their code. Sometimes they do, and there’s even a field in source code comments linking to outreach efforts, but consider the economics of that.&lt;/p&gt;&lt;p&gt;A browser vendor’s job is to make the web work for users, and if a popular site is broken in their main browser yet works in Chrome, users blame the browser. Filing a bug with a third party and waiting weeks or months for a fix that may never come is a losing proposition when you can ship a five-line workaround tomorrow.&lt;/p&gt;&lt;p&gt;There’s also the question of who you’d even contact. The developer who wrote the broken code might have left the company years ago, the team that owns that endpoint might not know it’s their responsibility, and the site might be in maintenance mode receiving security patches but nothing else. From the browser’s perspective, the choice is simple: fix it now, invisibly, and save everyone the trouble.&lt;/p&gt;&lt;p&gt;A WebKit engineer &lt;a href=&quot;https://www.otsukare.info/2023/01/16/webkit-quirks&quot;&gt;wrote a blog post&lt;/a&gt; about removing a quirk for FlightAware. The site was comparing CSS transform matrix strings, but the CSS spec had changed how browsers should serialize the values, and the browser became compliant, FlightAware broke, and engineers added domain-specific code to fix it. Outreach eventually worked, FlightAware fixed their code, and the quirk was removed. But for months, Safari users had a working experience only because someone wrote an &lt;code&gt;if&lt;/code&gt; statement in the browser checking for &lt;code&gt;flightaware.com&lt;/code&gt;.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;Check Yourself&lt;/h2&gt;&lt;p&gt;Your site might be getting special rendering treatment and you might not be aware of it. That quirk you’re benefiting from doesn’t show up in your error logs, and there’s no console warning that says “this browser is working around your mistakes.” The fix is invisible by design.&lt;/p&gt;&lt;p&gt;If you test mostly in Chrome, you’re especially exposed. Your site might work perfectly not because you wrote good code but because Chrome’s behavior aligns with your assumptions. Other browsers will have to choose between letting your site break for their users or adding you to their quirks file.&lt;/p&gt;&lt;p&gt;Open your site in Firefox and Safari. Not occasionally, not before a big launch, &lt;em&gt;regularly&lt;/em&gt;. The quirks files exist because developers didn’t do that.&lt;/p&gt;&lt;p&gt;If you find your domain in one, consider auditing whatever it was they worked around. Not because you have to (after all, the web kept working without your intervention) but because somewhere an engineer at a browser you don’t use solved a problem you didn’t know you had.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;The Web We Want vs The One We Have&lt;/h2&gt;&lt;p&gt;The specs are the map, but the quirks lists are the messy terrain.&lt;/p&gt;&lt;p&gt;Standards were supposed to eliminate browser-specific code. We dug ourselves out of the IE era, celebrated, and then built exactly the same hole again around a different browser. Only now the browser-specific code lives in the browsers that aren’t dominant, patching over a web built for the one that is.&lt;/p&gt;&lt;p&gt;Sites I’ve worked on are in these files. Yours might be too.&lt;br/&gt;
&lt;em&gt;And the lists are getting longer&lt;/em&gt;.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;Back to top&lt;/p&gt;&lt;hr/&gt;</content:encoded>
</item>
<item>
<title>The</title>
<link>https://hacktivis.me/articles/no-noscript-element</link>
<guid isPermaLink="false">Bh2U7w4Xge5WRA5wv4fEILgQwucH0mwDIh9jIg==</guid>
<pubDate>Thu, 14 May 2026 18:05:10 +0000</pubDate>
<description>One of the few traps of the web is how the &lt;noscript&gt;</description>
<content:encoded>&lt;p&gt;
		One of the few traps of the web is how the &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt;
		element doesn&amp;#39;t provides the right behavior.
	&lt;/p&gt;&lt;p&gt;
		Definition: The &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; element provides alternate content when JavaScript is entirely toggled off or entirely unsupported.&lt;br/&gt;
		Sources:
		&lt;/p&gt;&lt;ul&gt;
			&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/html4/interact/scripts.html#h-18.3.1&quot;&gt;HTML4 § 18.3.1 The NOSCRIPT element&lt;/a&gt; with ignoring non-JavaScript in &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; (W3C gives Tcl as example, I&amp;#39;d more point to JScript and VBScript, both of which are thankfully gone)&lt;/li&gt;
			&lt;li&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/scripting.html#the-noscript-element&quot;&gt;WHATWG HTML (multipage) § The noscript element&lt;/a&gt;&lt;/li&gt;
		&lt;/ul&gt;&lt;p&gt;
		While the way to obtain the right behavior is to have a generic
		textual element being updated/deleted by your own script using
		the DOM APIs.
		You could also make the scripts so optional for you
		to not need to provide a failure message,
		but that&amp;#39;s not the right method when the scripts are
		needed for actually using the webpage.
	&lt;/p&gt;&lt;p&gt;
		And should be noted that WHATWG HTML contains a similar recommendation to the latter method:
		&lt;/p&gt;&lt;blockquote&gt;
			The &lt;code&gt;noscript&lt;/code&gt; element is a blunt instrument.  Sometimes, scripts might
			be enabled, but for some reason the page&amp;#39;s script might fail.
			For this reason, it&amp;#39;s generally better to avoid using &lt;code&gt;noscript&lt;/code&gt;,
			and to instead design the script to change the page from being
			a scriptless page to a scripted page on the fly, […]
		&lt;/blockquote&gt;&lt;p&gt;
		Because the problem is, JavaScript can fail to load in several ways. Here&amp;#39;s a non-exhaustive list of cases:
		&lt;/p&gt;&lt;ul&gt;
			&lt;li&gt;Blocked domains/URLs, think adblockers/anti-viruses or corporate firewalls&lt;/li&gt;
			&lt;li&gt;Blocked browsers/users, think hosting-side firewall (at any of the IP/TCP/HTTP/… level of encapsulation) which can be legitimate, over-zealous, or accidental&lt;/li&gt;
			&lt;li&gt;Basic connectivity issues, after all browsing using mobile data is usual, HTTPS configurations still routinely presents expired certificates, BGP/DNS/… still fails&lt;/li&gt;
			&lt;li&gt;Unsupported APIs or even language syntax, either due to a browser not supporting bleeding edge features or non-portable features&lt;/li&gt;
			&lt;li&gt;Badly restored website backup or incomplete one&lt;/li&gt;
			&lt;li&gt;Badly deployed website update, think HTML updated before JavaScript or even an incomplete deployment&lt;/li&gt;
			&lt;li&gt;Part of the hosting infrastructure being down or overloaded&lt;/li&gt;
			&lt;li&gt;Limits from the hoster, like rate-limits or limits on total bandwidth&lt;/li&gt;
		&lt;/ul&gt;
		I see most of the above on a regular basis, about multiple times per week to few times a month, and I tend to browse simple websites.
		And that&amp;#39;s without counting the various ways scripts tend to fail at properly handling errors.</content:encoded>
</item>
<item>
<title>Comment désactiver l’IA dans Chrome pour retirer le modèle de 4 Go installé sans autorisation ?</title>
<link>https://www.numerama.com/tech/2253009-comment-desactiver-lia-dans-chrome-pour-retirer-le-modele-de-4-go-installe-sans-autorisation.html</link>
<guid isPermaLink="false">IFgh7lmH2fBBrKkwC3rBlY8s_sQRB8wyc8qZ_Q==</guid>
<pubDate>Thu, 14 May 2026 06:45:39 +0000</pubDate>
<description>L&#39;affaire du modèle d&#39;IA de 4 Go installé sans crier gare dans Google Chrome a fait couler beaucoup d&#39;encre. Depuis, une méthode a émergé pour couper cette fonctionnalité.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2024/09/google.jpg?resize=1200,675&amp;amp;key=0015ed43&amp;amp;watermark&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
L&amp;#39;affaire du modèle d&amp;#39;IA de 4 Go installé sans crier gare dans Google Chrome a fait couler beaucoup d&amp;#39;encre. Depuis, une méthode a émergé pour couper cette fonctionnalité.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>« Ni crédible ni attractive » : eBay refuse l’offre de rachat à 56 milliards de dollars de GameStop</title>
<link>https://www.numerama.com/tech/2245805-ni-credible-ni-attractive-ebay-refuse-loffre-de-rachat-a-56-milliards-de-dollars-de-gamestop.html</link>
<guid isPermaLink="false">x2BtXoQ9dHt4_XJDZE0KcoiqqvfTbWXVFRh6gw==</guid>
<pubDate>Thu, 14 May 2026 06:45:39 +0000</pubDate>
<description>Le 3 mai 2026, GameStop a créé la surprise en annonçant une OPA non sollicitée sur eBay, valorisée à 55,5 milliards de dollars. Une proposition refusée par eBay le 12 mai 2026, qui a jugé l&#39;offre « ni crédible, ni attractive ».</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/05/game-stop-x-ebay.jpg?resize=1200,675&amp;amp;key=46b12e69&amp;amp;watermark&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Le 3 mai 2026, GameStop a créé la surprise en annonçant une OPA non sollicitée sur eBay, valorisée à 55,5 milliards de dollars. Une proposition refusée par eBay le 12 mai 2026, qui a jugé l&amp;#39;offre « ni crédible, ni attractive ».
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Adieu les feux rouges : les nouveaux CAPTCHA de Google vont vous agacer</title>
<link>https://www.numerama.com/tech/2250617-adieu-les-feux-rouges-les-nouveaux-captcha-de-google-vont-vous-agacer.html</link>
<guid isPermaLink="false">wzrwAwhO_krUdWO4VshyeOU5m7yOxV_ilpQ1Sg==</guid>
<pubDate>Thu, 14 May 2026 06:45:39 +0000</pubDate>
<description>Déployée depuis avril 2026, la nouvelle génération de reCAPTCHA de Google remplace parfois les traditionnels CAPTCHA par une vérification via smartphone et QR code. Une évolution pensée pour contrer les bots dopés à l’IA, mais qui risque aussi d’exclure certains utilisateurs.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/05/1-15.jpg?resize=1200,675&amp;amp;key=6e901828&amp;amp;watermark&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Déployée depuis avril 2026, la nouvelle génération de reCAPTCHA de Google remplace parfois les traditionnels CAPTCHA par une vérification via smartphone et QR code. Une évolution pensée pour contrer les bots dopés à l’IA, mais qui risque aussi d’exclure certains utilisateurs.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Free en panne ? Vous n’êtes pas seuls, mais attention à l’effet loupe des sites de signalement</title>
<link>https://www.numerama.com/tech/2250925-free-en-panne-vous-netes-pas-seuls-mais-attention-a-leffet-loupe-des-sites-de-signalement.html</link>
<guid isPermaLink="false">DtwnOTx7dCtQMwnl26ytXLc5zv0MZXvFTY0UAA==</guid>
<pubDate>Thu, 14 May 2026 06:45:39 +0000</pubDate>
<description>Des signalements remontent ce 11 mai 2026 sur DownDetector sur Free, mais aussi OVH et Orange. À l&#39;arrivée, Free semble avoir des soucis, mais pour les autres la carte ressemble moins à une panne géante qu&#39;à une mosaïque d&#39;incidents locaux et de maintenances programmées.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/03/gemini-generated-image-5xqefy5xqefy5xqe.jpg?resize=1200,675&amp;amp;key=8cb8fd9c&amp;amp;watermark&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Des signalements remontent ce 11 mai 2026 sur DownDetector sur Free, mais aussi OVH et Orange. À l&amp;#39;arrivée, Free semble avoir des soucis, mais pour les autres la carte ressemble moins à une panne géante qu&amp;#39;à une mosaïque d&amp;#39;incidents locaux et de maintenances programmées.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Ces sites pour suivre la propagation de l’hantavirus sont-ils fiables ?</title>
<link>https://www.numerama.com/sciences/2250529-ces-sites-pour-suivre-la-propagation-de-lhantavirus-sont-ils-fiables.html</link>
<guid isPermaLink="false">nu80FEi3UR3TEjyIANXnW6jkGAjh64qRrb7aOA==</guid>
<pubDate>Thu, 14 May 2026 06:45:39 +0000</pubDate>
<description>Alors que le nombre de cas confirmés ou suspectés de contamination à l&#39;hantavirus augmente, des sites de « trackers » apparaissent pour suivre la diffusion du virus à travers le monde. Mais s&#39;ils peuvent être utiles pour visualiser l&#39;évolution, ils restent très incomplets.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/05/carte-hantavirus.jpg?resize=1200,675&amp;amp;key=7bee9af4&amp;amp;watermark&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Alors que le nombre de cas confirmés ou suspectés de contamination à l&amp;#39;hantavirus augmente, des sites de « trackers » apparaissent pour suivre la diffusion du virus à travers le monde. Mais s&amp;#39;ils peuvent être utiles pour visualiser l&amp;#39;évolution, ils restent très incomplets.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>5 Years and $5M Later: Inventing a New Programming Language for Web Development Was a Mistake | Wasp</title>
<link>https://wasp.sh/blog/2026/05/13/new-language-for-web-dev-was-a-mistake</link>
<enclosure type="image/jpeg" length="0" url="https://wasp.sh/img/new-language-mistake/banner.webp"></enclosure>
<guid isPermaLink="false">b7YHp8-mC7485a2J1k5dM5L9Xvh4RjYBNxmzAA==</guid>
<pubDate>Thu, 14 May 2026 03:45:31 +0000</pubDate>
<description>After 5 years and $5M building Wasp, we&#39;re replacing our custom config language with TypeScript. Here&#39;s why we thought a new language was a good idea, what we learned, and why TypeScript won.</description>
<content:encoded>&lt;p&gt;At &lt;a href=&quot;https://github.com/wasp-lang/wasp&quot;&gt;Wasp&lt;/a&gt;, we&amp;#39;re building a full-stack web framework - think Rails / Laravel for JS, but &amp;quot;stretched&amp;quot; over the frontend, too. My twin brother and I started it back in 2021 when we went through Y Combinator, and raised over $5M in total.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/hn-launch.webp&quot; alt=&quot;Wasp&amp;#39;s Launch HN post on Hacker News from February 2021&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Our initial idea was to build a new programming language that would abstract common web app patterns while also working with any stack (we started with React, Node.js and Prisma) when you need to go deeper. Think Terraform, but for your web app stack instead of cloud infrastructure.&lt;/p&gt;&lt;p&gt;Five years in, we realize it was a mistake. Creating a new language makes sense for certain problems and domains, but in this case, it wasn&amp;#39;t a fit and brought us more trouble than it was worth.&lt;/p&gt;&lt;p&gt;This post is about why we thought it was a good idea, what we learned, and why we&amp;#39;re replacing our custom language with TypeScript, while Wasp itself stays the same under the hood.&lt;/p&gt;&lt;h2&gt;TL;DR​&lt;/h2&gt;&lt;p&gt;This is a long post. Here are the key things covered, so you can jump to what interests you without reading everything:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Having switched web dev stacks for years, we thought it would be cool to create a &amp;quot;universal framework&amp;quot; that could work with any stack. We concluded we need to create a new language to achieve that.&lt;/li&gt;
&lt;li&gt;Developers resonated with the problem Wasp was solving, but the language was a tough sell. &amp;quot;Lang&amp;quot; in the name made them think our aim was to replace JavaScript (it wasn&amp;#39;t) and were skeptical of how it&amp;#39;d work with their tooling.&lt;/li&gt;
&lt;li&gt;Many developers loved Wasp once they gave it a try. But getting them to try it out was the hard part.&lt;/li&gt;
&lt;li&gt;Adoption still grew, but as we kept pushing towards 1.0 we realised the &amp;quot;language&amp;quot; concern wasn&amp;#39;t going away. Also, maintaining a good IDE experience for a custom language proved to be way harder than expected.&lt;/li&gt;
&lt;li&gt;It turned out having a custom language wasn&amp;#39;t Wasp&amp;#39;s core value. It was maintaining a high-level specification of the entire full-stack app, making it easier to reason about for both humans and AI.&lt;/li&gt;
&lt;li&gt;We decided to replace Wasp&amp;#39;s config language with TypeScript. It is &amp;quot;only&amp;quot; an interface change, while everything else stays the same.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Why we thought creating a new language was a good idea​&lt;/h2&gt;&lt;p&gt;My brother and I both come from a traditional Computer Science background, where we focused mostly on algorithms and applying them to areas such as bioinformatics and machine learning. We never considered ourselves web developers, and were even somewhat opposed to it (&lt;em&gt;&amp;quot;look at these folks just painting buttons the whole day, while I&amp;#39;m writing all the cool algorithms.&amp;quot;&lt;/em&gt;). Obviously, we didn&amp;#39;t know enough.&lt;/p&gt;&lt;p&gt;Still, we had one entrepreneurial friend in our group who was always doing something on the side, and he would pull us in whenever he&amp;#39;d land a bigger project. Each of these projects had something that made it &amp;quot;interesting&amp;quot; for us (e.g., building a news portal that also needed a recommendation engine, or an option trading analytics platform), but at the end of the day, it was mostly a classic web development job.&lt;/p&gt;&lt;p&gt;That way, we got exposed to things like PHP (we built a complete CMS from scratch in it and then later realized there were frameworks and even off-the-shelf solutions we could have used), Java/JBoss, and also Backbone.js, Angular.js, and React as they were popping up, along with the build tools (remember Bower, Grunt, and Gulp)?&lt;/p&gt;&lt;p&gt;2013 and later marked the era of reactive front-end frameworks, and each new one was something you absolutely had to use in your latest project, because that was part of what made your project/startup cool. The result was that each new project we started, we started with almost a completely different stack from the last one and had to re-learn all the patterns that we just figured out - authentication, routing, state management, …&lt;/p&gt;&lt;h3&gt;We got tired of switching and stitching stacks​&lt;/h3&gt;&lt;p&gt;Before, one would just choose between Spring Boot, Django, or Rails and have everything handled and managed. Now, I had to first pick each library, then make React, Redux, Webpack, Express, Passport, and Sequelize work together.&lt;/p&gt;&lt;p&gt;No system was in charge, with a full understanding of the web app, and that feeling of responsibility didn&amp;#39;t really sit well with Martin and me.&lt;/p&gt;&lt;p&gt;Having gone through this stack-switching dance a few times (eventually landing on React, Node.js, and Mongoose at the time), we thought - isn&amp;#39;t there a better way? It felt like we were constantly reinventing the same thing, spending most of our time managing our stack rather than writing our unique business logic.&lt;/p&gt;&lt;p&gt;There&amp;#39;s a name for this: &lt;em&gt;accidental complexity&lt;/em&gt;. The work that has nothing to do with your actual problem, but you can&amp;#39;t avoid it because of the tools you&amp;#39;re using. That&amp;#39;s exactly what we were feeling.&lt;/p&gt;&lt;h3&gt;What if I could just say what I want, once?​&lt;/h3&gt;&lt;p&gt;We asked ourselves: what if you could simply write what you want, and that&amp;#39;s it? For example:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;quot;I want my app to have authentication with Google and GitHub&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;quot;I want to have a route /profile, which is available only to authenticated users&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;quot;I want a cron job that runs this function every day at 5pm&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Obviously, this is too conversational-y, so we imagined boiling it down to something more structured, like:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auth: Google, GitHub&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;page Profile -&amp;gt; /profile, authRequired: true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;job updateStats: run function doSomeCalc from stats.js every day at 5pm&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;It would basically be a way to define your app&amp;#39;s requirements at a higher, implementation-free level (aka specification). We felt something like this could and should be possible, but with the tools we had, we had to implement each of these requirements scattered across the stack at what felt like an abstraction level too low.&lt;/p&gt;&lt;p&gt;Our goal was never to replace the existing stack. You&amp;#39;d still implement most of your logic in a specific runtime (e.g., React or Node.js). But there&amp;#39;d be a central backbone which holds everything together.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/wasp-architecture.webp&quot; alt=&quot;Wasp architecture diagram&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;How we imagined (and implemented) Wasp. The config .wasp file with high-level declarations + your custom logic in React &amp;amp; Node.js goes in, and the fully working full-stack app is produced as a build artefact.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;If you&amp;#39;re curious, this is a code excerpt showing what the DSL looked like in its final form:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot;&gt;app todoApp {
  title: &amp;quot;ToDo App&amp;quot;,  // visible in the browser tab
  auth: { // full-stack auth out-of-the-box
    userEntity: User,
    methods: { google: {}, gitHub: {}, email: {...} }
  }
}

route RootRoute { path: &amp;quot;/&amp;quot;, to: MainPage }
page MainPage {
  authRequired: true, // Limit access to logged in users.
  component: import Main from &amp;quot;@client/Main&amp;quot; // &amp;lt;-- Your React code.
}

query getTasks {
  fn: import { getTasks } from &amp;quot;@server/tasks&amp;quot;, // &amp;lt;-- Your Node.js code.
	entities: [Task] // Automatic cache invalidation.
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The key insight for us was that the &lt;strong&gt;web app domain changes very slowly compared to the velocity at which web dev implementation techniques evolve&lt;/strong&gt;. Both ten years ago and today, we refer to pages, routes, API endpoints, and data models. In the meantime, the UI implementation went full circle from server templates to the rich client and back to server actions.&lt;/p&gt;&lt;p&gt;Our goal with Wasp was to let developers express as much as possible at the domain level, and let the system handle the (latest) implementation details. That way, whatever they build will last much longer and be easier to understand.&lt;/p&gt;&lt;h3&gt;Why create a new language from scratch? Why not use the existing ones?​&lt;/h3&gt;&lt;p&gt;While our understanding of the problem has mostly proven correct, because we experienced it first-hand, this was where we took the wrong turn.&lt;/p&gt;&lt;p&gt;There were two main reasons why we opted to design a new language from scratch, with its own custom compiler, rather than use, e.g., TypeScript or Python as a &amp;quot;host&amp;quot; language:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;We wanted to have a full control over the syntax&lt;/strong&gt;, and make it as clean and boilerplate-free as possible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;We envisioned Wasp as a runtime-agnostic tool&lt;/strong&gt;, eventually. E.g. maybe you write a piece of a typical CRUD logic in Node.js, but then some data-heavy stuff you&amp;#39;d rather do in Python which has better libraries for it.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;While we considered using TypeScript (and that was some of the early feedback we got, to have an embedded DSL, which is basically just a library, instead of the explicit DSL), it felt &amp;quot;wrong&amp;quot; to us at the time, as if we&amp;#39;d betray the core principles and a big vision we had for Wasp.&lt;/p&gt;&lt;p&gt;In a way, we were very excited to ship Wasp as a standalone language because we believed it would make a stronger statement and make it clear it&amp;#39;s not another typical language-bound framework (e.g., Rails or Django), but something more general.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/first-landing-page.webp&quot; alt=&quot;Wasp first landing page&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;Our first landing page, back in 2021. We were proudly putting &amp;quot;language&amp;quot; front and center.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;To be completely honest, we were also excited about the fact that we get to work on something so &amp;quot;cool&amp;quot; and foundational, such as a language and a compiler. Martin and I are both Haskell aficionados, and this was the perfect nail to hit with our functional hammer.&lt;/p&gt;&lt;h2&gt;Reception: You loved the idea and tolerated the language​&lt;/h2&gt;&lt;p&gt;After about &lt;a href=&quot;https://wasp.sh/blog/2022/09/29/journey-to-1000-gh-stars&quot;&gt;a year of working on Wasp, releasing an Alpha version, and launching it to developers&lt;/a&gt;, we found a small tribe of excited early adopters and were accepted into Y Combinator. We raised our &lt;a href=&quot;https://techcrunch.com/2021/10/04/yc-grads-wasp-land-1-5m-seed-to-help-developers-build-web-apps-faster/&quot;&gt;pre-seed round&lt;/a&gt; shortly afterward, which enabled us to assemble the team and go build the first &amp;quot;real&amp;quot; version.&lt;/p&gt;&lt;p&gt;Things were slow at the beginning as we were getting everything off the ground, but developers acutely felt the pain of boilerplate and were tired of stack-stitching, so Wasp gained momentum, especially after we entered Beta.&lt;/p&gt;&lt;p&gt;You might also remember that at a similar time, back in 2021, a couple of other &amp;quot;Rails for JS&amp;quot; frameworks appeared on the scene, like RedwoodJS (by the creator of GitHub) and BlitzJS. They quickly got solid community traction, and it became clear to us that we&amp;#39;re on the track to solve a very important problem.&lt;/p&gt;&lt;div&gt;&lt;div&gt;Being weird sometimes pays off&lt;/div&gt;&lt;div&gt;&lt;p&gt;In a way, Wasp&amp;#39;s &amp;quot;weirdness&amp;quot; is what saved it from Redwood&amp;#39;s and Blitz&amp;#39;s fate. By avoiding tight coupling to a specific technology (as Redwood did with GraphQL and Blitz with Next.js), it remained general enough to adapt quickly and avoid becoming obsolete.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/github-stars.webp&quot; alt=&quot;GitHub stars growth&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Still, talking to developers, the question of &amp;quot;But why did you create a new language?&amp;quot; kept popping up in different contexts. There were a few main objections we kept hearing.&lt;/p&gt;&lt;h3&gt;I see &amp;quot;wasp-lang&amp;quot; - does this replace JavaScript?​&lt;/h3&gt;&lt;p&gt;Although we never meant to replace the existing web dev stack, but rather complement it (with Wasp, you still write 90% of your code in React &amp;amp; Node.js),  the notion of the &amp;quot;lang&amp;quot; suffix was simply too strong to convey that.&lt;/p&gt;&lt;p&gt;Whenever a developer read &amp;quot;wasp-lang.dev&amp;quot;, their mind automatically went to &amp;quot;oh, this is something like Rust/Java&amp;quot;, and it was very hard to remove that lens once it came up. It immediately put Wasp into the &amp;quot;looks cool, but it&amp;#39;s way too early&amp;quot; bucket.&lt;/p&gt;&lt;p&gt;We were very excited about building a language and even highlighted it, but we underestimated how strong the term&amp;#39;s meaning already is.&lt;/p&gt;&lt;h3&gt;Is this going to work with my IDE and existing tooling?​&lt;/h3&gt;&lt;p&gt;Even if you gave the language thing the benefit of the doubt, the next question is, &amp;quot;Does this come with its own ecosystem?&amp;quot; Developers know how much work it is to create a new standard and how much time it takes to grow the ecosystem around it.&lt;/p&gt;&lt;h3&gt;This isn&amp;#39;t for me, I don&amp;#39;t want to learn Haskell​&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/haskell-love.webp&quot; alt=&quot;Haskell love meme&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;We&amp;#39;re using Haskell internally for our compiler, but end users will never see it and will use only TypeScript. It&amp;#39;s similar to how Prisma&amp;#39;s core was initially developed in Rust, and Terraform&amp;#39;s HCL in Go.&lt;/p&gt;&lt;p&gt;Still, our initial marketing around using Haskell worked out a bit too well for our own good. The Haskell community is small, but engaged, enthusiastic, and very hungry for real-world projects, especially around developer tooling.&lt;/p&gt;&lt;p&gt;Sharing our progress with the community was often very well accepted, but also reinforced Wasp as a &amp;quot;Haskell-based language&amp;quot;, especially if you just glanced over the title.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/haskell-positioning.webp&quot; alt=&quot;Haskell positioning issue&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Pair that up with the fact that on the GitHub repo page in the &amp;quot;Languages&amp;quot; bar, it said &amp;quot;Haskell: 90%&amp;quot; (we later found a way to circumvent this), and you got yourself a perfectly executed wrong positioning.&lt;/p&gt;&lt;h3&gt;It&amp;#39;s the packaging issue​&lt;/h3&gt;&lt;p&gt;Many developers who actually tried Wasp loved it and didn&amp;#39;t mind the language much. They could ship faster than ever without spending weeks learning the stack, and keep using React &amp;amp; Node.js. But getting developers to make that leap from &lt;em&gt;&amp;quot;what is this?&amp;quot;&lt;/em&gt; to &lt;em&gt;&amp;quot;I will give it a go&amp;quot;&lt;/em&gt; was really hard.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/developer-love.webp&quot; alt=&quot;Developer testimonials about Wasp&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;Once they tried Wasp, many developers really loved it. The hard part was getting them to try it.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;We kept pushing. With Beta, adoption soared, and we started seeing startups built with Wasp getting acquired, as well as enterprises building internal tools and deploying them into their local infra setup (in the end, the Wasp app is just static frontend files + a single Docker image for the backend).&lt;/p&gt;&lt;p&gt;To get past the &amp;quot;this is a weird new framework, why should I try it?&amp;quot; barrier, we built products on top of Wasp (an &lt;a href=&quot;https://opensaas.sh/&quot;&gt;open-source SaaS boilerplate starter&lt;/a&gt; and &lt;a href=&quot;https://wasp.sh/blog/2023/07/10/gpt-web-app-generator&quot;&gt;our own &amp;quot;early&amp;quot; Lovable&lt;/a&gt;) that shifted the decision to a higher level. It worked great and brought many people to Wasp.&lt;/p&gt;&lt;p&gt;Still, the core issue persisted. Developers who came to Wasp couldn&amp;#39;t understand what we were building, so they couldn&amp;#39;t get excited about it.&lt;/p&gt;&lt;h3&gt;The final nail - trying to make IDE support work​&lt;/h3&gt;&lt;p&gt;We always assumed that if our language proves to be a wrong choice, it will be because our users told us so. It turned out to be the opposite: while developers who adopted Wasp were happy to keep using it, we started having more and more issues with it internally.&lt;/p&gt;&lt;p&gt;One of the main things we underestimated was how much work it takes to develop the necessary tooling for a custom language, especially IDE/editor support. The bar developers expect today, especially in the JS world, is incredibly high, and the line between an IDE and a compiler is getting blurred.&lt;/p&gt;&lt;p&gt;Once we dove deeper into building tooling for a custom language, we quickly realized the entire ecosystem is built for &amp;quot;standard&amp;quot; JavaScript and TypeScript frameworks. Anything else, and you&amp;#39;re on your own, hitting walls really fast.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/vscode-extension.webp&quot; alt=&quot;VS Code extension for Wasp language&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;VS Code extension for Wasp language&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;We ended up developing our own language server and a VS Code extension for it, but since Wasp used Prisma&amp;#39;s DSL as an embedded language and had many references to React &amp;amp; Node.js files, we still only reached 80% of where we wanted to be.&lt;/p&gt;&lt;h2&gt;Goodbye Wasp language, welcome TypeScript​&lt;/h2&gt;&lt;p&gt;With all the accumulated pain around maintaining a custom language, and the constant friction of getting new developers to give it a try, we realized the root cause is deeper than &amp;quot;developers are afraid to try a new web framework&amp;quot;.&lt;/p&gt;&lt;p&gt;The usage kept growing, and more and more people were shipping their Wasp apps to production, but whatever we tried, it would always ultimately come back to &amp;quot;I really like what you&amp;#39;re doing with Wasp, but why a custom language?&amp;quot;. It felt like we were constantly driving with a handbrake pulled up.&lt;/p&gt;&lt;p&gt;Finally, the ergonomics we aimed for with the language didn&amp;#39;t turn out to be as important as we thought. Developers are perfectly happy using TypeScript, a language they are familiar with, even if it requires a few extra lines and braces.&lt;/p&gt;&lt;h3&gt;Language was never the moat. It&amp;#39;s having a high-level understanding of your entire app at compile time.​&lt;/h3&gt;&lt;p&gt;When we started Wasp, the terms &amp;quot;language&amp;quot; and &amp;quot;specification&amp;quot; were basically synonyms to us. We couldn&amp;#39;t imagine one without the other.&lt;/p&gt;&lt;p&gt;But watching developers use Wasp and seeing what they are excited about, it became clear it was never the language. It was the fact that Wasp has a full understanding of their entire app via a high-level spec (e.g., &lt;code&gt;main.wasp&lt;/code&gt;, now &lt;code&gt;main.wasp.ts&lt;/code&gt;), which lets them easily grasp how their app works at a glance and feel in control.&lt;/p&gt;&lt;p&gt;One way in which we tried to make it more tangible is by having a &lt;code&gt;wasp studio&lt;/code&gt; command - if you run it in your terminal, you&amp;#39;ll see how Wasp &amp;quot;sees&amp;quot; your app at compile time, and can reason about it before generating the target code:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://wasp.sh/img/new-language-mistake/wasp-studio.webp&quot; alt=&quot;Wasp studio showing app structure&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;wasp studio - how Wasp &amp;quot;sees&amp;quot; the structure of your app&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Having this kind of &amp;quot;support system&amp;quot; made the process of building an app a much more managed experience. With AI and developers reviewing generated code less frequently, this became even more valuable, especially for a new generation of builders with less technical backgrounds (so-called &amp;quot;vibe-coders&amp;quot;).&lt;/p&gt;&lt;p&gt;And this app-level understanding doesn&amp;#39;t change at all with the switch from Wasp&amp;#39;s custom language to TypeScript. We only swapped the &amp;quot;front end&amp;quot; of the compiler, or how you define a high-level app spec in Wasp.&lt;/p&gt;&lt;h3&gt;TypeScript SDK - from an experiment to production​&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot;&gt;// Page &amp;amp; route definition
const loginPage = app.page(&amp;#39;LoginPage&amp;#39;, {
  component: { importDefault: &amp;#39;Login&amp;#39;, from: &amp;#39;@src/pages/auth/Login&amp;#39; }
});
app.route(&amp;#39;LoginRoute&amp;#39;, { path: &amp;#39;/login&amp;#39;, to: loginPage });

// Query
app.query(&amp;#39;getTasks&amp;#39;, {
  fn: { import: &amp;#39;getTasks&amp;#39;, from: &amp;#39;@src/queries&amp;#39; },
  entities: [&amp;#39;Task&amp;#39;]
});

// Async job
app.job(&amp;#39;mySpecialJob&amp;#39;, {
  executor: &amp;#39;PgBoss&amp;#39;,
  perform: {
    fn: { import: &amp;#39;foo&amp;#39;, from: &amp;#39;@src/jobs/bar&amp;#39; },
    executorOptions: {
      pgBoss: { retryLimit: 1 }
    }
  },
  entities: [&amp;#39;Task&amp;#39;]
});

export default app;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;We introduced the &lt;a href=&quot;https://wasp.sh/docs/general/wasp-ts-config&quot;&gt;TypeScript SDK for Wasp&lt;/a&gt; as an experimental, early preview feature to gauge feedback. And we&amp;#39;ve received very positive feedback - some of our new users immediately opted for it and never even tried using the Wasp language. This has been a signal for us to keep going.&lt;/p&gt;&lt;p&gt;After testing it internally and within the Wasp community, we&amp;#39;re now confident this is the way forward. Besides circumventing the initial &amp;quot;why a new language&amp;quot; question, it solves all the problems we&amp;#39;ve been fighting for years.&lt;/p&gt;&lt;p&gt;Every editor works out of the box. Developers can use conditionals, loops, and imports (e.g., you could implement your own file-based routing!). Splitting the spec across multiple files becomes trivial. And it gives us a solid foundation for the advanced features we&amp;#39;ve been wanting to build, like &lt;a href=&quot;https://github.com/wasp-lang/wasp/issues/2873&quot;&gt;Full Stack Modules&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;DSL, thanks for all the fish - we&amp;#39;d never come this far without you​&lt;/h2&gt;&lt;p&gt;Looking back, Wasp probably wouldn&amp;#39;t have come to life if we hadn&amp;#39;t started the way we did. It was our best way to express what we wanted to build at the time, paired up with some personal biases, and well, what we simply thought was fun. It was the curiosity of &amp;quot;We know this sounds like a crazy idea, but we really want to see if it can work&amp;quot; that kept us pushing even when no one was watching.&lt;/p&gt;&lt;p&gt;The DSL approach kept us honest to our vision that the specification should be separate from the implementation. We&amp;#39;re still curious about opening support for other languages and runtimes (e.g., Python or Rust) and about the benefits of having a full picture of your app at compile time and being able to reason about it before generating the target code (which opens space for supporting different architectures, optimizations, etc.).&lt;/p&gt;&lt;h2&gt;Your AI agent might like Wasp, too​&lt;/h2&gt;&lt;p&gt;Finally, as AI agents write more and more of our code, we see many developers looking for tools that provide more structure and opinions upfront, which in turn makes adding features more predictable and the generated code easier to understand and review.&lt;/p&gt;&lt;p&gt;Wasp fits perfectly here, since it spans the full stack of your app and ensures everything works together at all times. It&amp;#39;s why many developers have also found renewed love for &amp;quot;old-school&amp;quot; monolithic frameworks such as Django, Rails, and Laravel. Wasp aims to provide the same experience and that feeling of &amp;quot;it just all works&amp;quot;, but for the JS ecosystem.&lt;/p&gt;&lt;p&gt;We repeatedly hear from developers using Wasp that it is the stack that works best with AI, and some of them even went so far as to &lt;a href=&quot;https://wasp.sh/blog/2026/03/29/hireveld-from-10-stacks-to-production-with-wasp&quot;&gt;try 10 different solutions before settling on Wasp&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;We are shipping TypeScript-first Wasp soon​&lt;/h2&gt;&lt;p&gt;In the next few weeks, we&amp;#39;re hosting Launch Week, during which we&amp;#39;ll ship the TypeScript SDK as the primary way to use Wasp. We&amp;#39;ve already seen the excitement within our community, and can&amp;#39;t wait to hear what you think about it.&lt;/p&gt;&lt;p&gt;If you&amp;#39;ve ever been curious about Wasp, now is the perfect time to give it a try. No new language to learn. Just TypeScript, and everything Wasp does for you under the hood.&lt;/p&gt;&lt;p&gt;To stay in the loop, join our &lt;a href=&quot;https://discord.gg/rzdnErX&quot;&gt;Discord&lt;/a&gt;, or follow &lt;a href=&quot;https://twitter.com/WaspLang&quot;&gt;Wasp&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/MatijaSosic&quot;&gt;me&lt;/a&gt; on X/Twitter.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Soon We Can Finally Banish JavaScript to the ShadowRealm | CSS-Tricks</title>
<link>https://css-tricks.com/soon-we-can-finally-banish-javascript-to-the-shadowrealm/</link>
<enclosure type="image/jpeg" length="0" url="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/04/js-shadow.png"></enclosure>
<guid isPermaLink="false">y3dDzNy6tBWBKetRdQpahkEz_Jxb6LKrw_5RrA==</guid>
<pubDate>Wed, 13 May 2026 04:20:55 +0000</pubDate>
<description>The proposed ShadowRealm API introduces a new kind of realm specifically designed for isolation, and only that.</description>
<content:encoded>&lt;p&gt;It’s gonna be tough to keep it together on this one. Okay. I got this. I am a &lt;em&gt;professional technical writer&lt;/em&gt;. Straight face; all-business. Ahem: if you’ve been following the ongoing work at TC39 (the standards body responsible for maintaining and developing the standards that inform JavaScript) you may have encountered some of &lt;a href=&quot;https://github.com/tc39/proposal-shadowrealm&quot;&gt;their recent work on ShadowRealms&lt;/a&gt;— &lt;em&gt;snrk&lt;/em&gt;. Sorry! Sorry, I’m good! Just, whew ­— what a name, “ShadowRealms.” Okay, hang on, let me start at the beginning. Maybe that will help.&lt;/p&gt;&lt;p&gt;It’s exceptionally likely you’ve seen JavaScript described as “single-threaded” at some point — that’s usually pretty high up on the list of JavaScript fundamentals, alongside “case sensitive,” “whitespace insensitive,” and “bad at math.” That is &lt;em&gt;correct&lt;/em&gt;, in the strict “computer science” sense, but it still gets my hackles up a little whenever I see it.&lt;/p&gt;&lt;p&gt;I mean, accurate in that JavaScript isn’t multi-threaded, for sure. A script is always executed in a very linear way — top to bottom, left to right, one execution context after another, winding up the call stack and then back down again. It’s just that you eventually come to learn about something like &lt;a href=&quot;https://css-tricks.com/off-the-main-thread/&quot;&gt;Web Workers&lt;/a&gt;, which — not to put too fine a point on this — allow you to execute JavaScript code in &lt;em&gt;another thread&lt;/em&gt;. That’s where I think “JavaScript is single-threaded” becomes a less helpful framing, because even though JavaScript isn’t a multi-threaded &lt;em&gt;language&lt;/em&gt;, a JavaScript &lt;em&gt;application&lt;/em&gt; can make use of multiple threads.&lt;/p&gt;&lt;p&gt;It’s a better framing — and every bit as technically accurate — to say that a JavaScript &lt;strong&gt;realm&lt;/strong&gt; is single-threaded. A realm refers to the environment where code is executed: a browser tab is a realm, and within that realm is the single thread where JavaScript is executed — the &lt;strong&gt;main thread&lt;/strong&gt;. A Web Worker is a realm with a &lt;strong&gt;worker thread&lt;/strong&gt;. JavaScript running in a cross-origin &lt;code&gt;iframe&lt;/code&gt; is running in &lt;em&gt;that&lt;/em&gt; &lt;code&gt;iframe&lt;/code&gt; realm’s main thread. We can’t, for example, offload the execution of a single function to another thread — JavaScript is &lt;em&gt;itself&lt;/em&gt; single-threaded, as a language. But a JavaScript application can span multiple realms and make use of multiple execution threads, and each of those realms can communicate with other realms in specific ways.&lt;/p&gt;&lt;p&gt;Each JavaScript realm has its own global environment. In a browser tab, the global object is the &lt;code&gt;Window&lt;/code&gt; interface. The same is true in a non-same-origin &lt;code&gt;iframe&lt;/code&gt; within that browser tab — the global object is the &lt;code&gt;Window&lt;/code&gt; “owned” by that &lt;code&gt;iframe&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;iframe id=&amp;quot;theIframe&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;
  &amp;lt;/body&amp;gt;

  &amp;lt;script&amp;gt;
  ( () =&amp;gt; {
    console.log( window.globalThis );
    // Result: Window {}

    console.log( theIframe.contentWindow.globalThis );
    // Result: Window {}
  })();

  &amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These aren’t the &lt;em&gt;same&lt;/em&gt; global object:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;iframe id=&amp;quot;theIframe&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;
  &amp;lt;/body&amp;gt;

  &amp;lt;script&amp;gt;
  ( () =&amp;gt; {
    console.log( window.globalThis === theIframe.contentWindow.globalThis );
    // Result: false
  })();
  &amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The outer page and the inner &lt;code&gt;iframe&lt;/code&gt; are two separate realms, both single-threaded, each with their own global objects and their own intrinsic objects:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;iframe id=&amp;quot;theIframe&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;
  &amp;lt;/body&amp;gt;

  &amp;lt;script&amp;gt;
    (() =&amp;gt; {
      console.log( window.Array );
      /* Result (expanded):
        function Array()
        from: function from()
        fromAsync: function fromAsync()
        isArray: function isArray()
        length: 1
        name: &amp;quot;Array&amp;quot;
        of: function of()
        prototype: Array []
        Symbol(Symbol.species): undefined
        &amp;lt;prototype&amp;gt;: function ()
      */

      console.log( theIframe.contentWindow.Array );
      /* Result (expanded):
        function Array()
        from: function from()
        fromAsync: function fromAsync()
        isArray: function isArray()
        length: 1
        name: &amp;quot;Array&amp;quot;
        of: function of()
        prototype: Array []
        Symbol(Symbol.species): undefined
        &amp;lt;prototype&amp;gt;: function ()
      */

      console.log( window.Array === theIframe.contentWindow.Array );
      // Result: false

    })();
  &amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, as you might expect, any global properties defined in the context of one realm will be unavailable to another:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;iframe id=&amp;quot;theIframe&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;
  &amp;lt;/body&amp;gt;

  &amp;lt;script&amp;gt;
  function globalFunction() {};

  console.log( window.globalFunction );
  // Result: function globalFunction()

  console.log( theIframe.contentWindow.globalFunction );
  // Result: undefined
  &amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;“Unavailable” — or, depending on how you look at it, unable to &lt;em&gt;interfere&lt;/em&gt; with the the global object of another realm. If you’ve been JavaScripting for a while, you know that no matter how meticulous we are about managing scope, the global environment can get pretty messy despite our best efforts. Some of that is on us, sure — a stray variable binding happens to the best of us — but a lot of that clutter is a result of the early design decisions that went into the language itself, like the function declaration in the previous example. When you consider the staggering amount of JavaScript we don’t control that can get piled onto the average project — from frameworks to third-party helper libraries to polyfills to user analytics to advertisements — there’s potential for collisions, to say the least.&lt;/p&gt;&lt;p&gt;Given the global scope pollution that has haunted the language since time immemorial (the 90s), it isn’t hard to imagine the use cases for offloading code to a realm that can act as a sandbox for the execution of JavaScript we don’t want to impact, or be impacted by, whatever is already cluttering up the global scope. We might want to run part of our test suite in a “clean room” where &lt;em&gt;performing&lt;/em&gt; the testing can’t potentially interfere with the results of your testing and mock data can’t run afoul of the real thing, or a place to run code we want quarantined away from the realm that contains our JavaScript application itself to prevent third-party libraries that don’t &lt;em&gt;need&lt;/em&gt; access to the global environment from cluttering it up, to no benefit.&lt;/p&gt;&lt;p&gt;We can’t do that with realms, as they stand right now — remember, JavaScript is single-threaded in that each &lt;em&gt;realm&lt;/em&gt; is single-threaded, and communication between those threads is limited. As undeniable as the use case is, we can’t repurpose an alternate realm to execute code on its single thread of execution, then weave the results of that execution back into the main thread of our primary realm. That’s multi-threaded execution by definition, and not just contrary to the fundamental nature of JavaScript, but, well, let me put it this way: JavaScript allowing multiple threads of execution at the same time &lt;em&gt;mean would problems us new for&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;To offload code in this way would require a new &lt;em&gt;kind&lt;/em&gt; of realm — one that has its own global and intrinsic objects, but &lt;em&gt;not&lt;/em&gt; it’s own thread — a realm where code offloaded to it will still be executed on the main thread of the realm that “owns” that script. A dark reflection of our own realms; a realm the light can never touch, where only fleeting, ephemeral shadows of our banished code can dwell! Imagine a distant peal of thunder, here; maybe also imagine that I’m wearing a cape, maybe I hurl a wine glass to the floor. Y’know, have fun with it. How could you not? I mean, they’re called:&lt;/p&gt;&lt;h3&gt;ShadowRealms&lt;/h3&gt;&lt;p&gt;The proposed &lt;a href=&quot;https://github.com/tc39/proposal-shadowrealm&quot;&gt;ShadowRealm API&lt;/a&gt; introduces a new kind of realm specifically designed for &lt;em&gt;isolation&lt;/em&gt;, and only that. A ShadowRealm does &lt;em&gt;not&lt;/em&gt; have an execution context of its own — code offloaded to a ShadowRealm will exist in a pseudo-realm with its own global and built-in objects. That code continues to run on the same thread as the code where the ShadowRealm is created; we’re not forced to communicate and share resources back and forth between two separate threads in limited ways. In short, a script is executed the way it would if limited to a single realm, but quarantined away from that outer realm’s intrinsic objects, APIs, global object, and anything our script has &lt;em&gt;done&lt;/em&gt; to that global object.&lt;/p&gt;&lt;p&gt;That sounds complicated, but the proposed API would be exceptionally simple in practice:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// Create a ShadowRealm:
const shadow = new ShadowRealm();

function globalFunction() {};

console.log( globalthis.globalFunction );
// Result: function globalFunction()

// Evaluate `globalThis.globalFunction` inside the ShadowRealm:
console.log( shadow.evaluate( &amp;#39;globalThis.globalFunction&amp;#39; ) );
// Result: undefined&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Keep in mind that this code is still theoretical — it doesn’t exist in the ES-262 standard or browsers just yet.&lt;/p&gt;&lt;p&gt;&lt;code&gt;globalFunction&lt;/code&gt; is defined on the outer realm’s global object just like we saw earlier, but it isn’t defined on the global object inside of our newly-created ShadowRealm — that ShadowRealm’s global object remains pristine, no matter what we do &lt;em&gt;outside&lt;/em&gt; of it. The inverse is true, naturally:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// Create a ShadowRealm:
const shadow = new ShadowRealm();

// Declare a global function inside the ShadowRealm:
shadow.evaluate( &amp;#39;function globalFunction() {};&amp;#39; );

// It doesn&amp;#39;t exist in the outer realm&amp;#39;s global object:
console.log( globalthis.globalFunction );
// Result: undefined

// But when we evaluate `globalThis.globalFunction` inside the ShadowRealm:
console.log( shadow.evaluate( &amp;#39;globalThis.globalFunction&amp;#39; ) );
// Result: function globalFunction()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We’ve declared that function inside the ShadowRealm, and we can call it by way of the variable that references that ShadowRealm object. That function remains quarantined away from the outer global object and that of any other ShadowRealm:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// Create a ShadowRealm:
const firstShadow = new ShadowRealm();
const secondShadow = new ShadowRealm();

// Declare a global function inside the ShadowRealm referenced by `secondShadow`:
secondShadow.evaluate( &amp;#39;function globalFunction() {};&amp;#39; );

// It doesn&amp;#39;t exist in the outer realm&amp;#39;s global object:
console.log( globalthis.globalFunction );
// Result: undefined

// It doesn&amp;#39;t exist in the global object of the ShadowRealm referencd by `firstShadow`:
console.log( firstShadow.evaluate( &amp;#39;globalThis.globalFunction&amp;#39; ) );
// Result: undefined

// It only exists within the ShadowRealm referenced by `secondShadow`:
console.log( secondShadow.evaluate( &amp;#39;globalThis.globalFunction&amp;#39; ) );
// Result: function globalFunction()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;“Quarantined” to an extent, that is. ShadowRealms don’t provide a true security boundary in that code running inside a ShadowRealm can still make inferences about code running in other realms. They &lt;em&gt;can&lt;/em&gt; be thought of as an &lt;em&gt;integrity&lt;/em&gt; boundary, in that code running inside a ShadowRealm can’t directly interfere with another realm — unless we let it, of course. Even though code shunted off into a ShadowRealm can’t interfere with the objects outside of it, we’re still free to use the results of those operations the way we would use the results of that same operation in the host realm:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// Create a ShadowRealm:
const shadow = new ShadowRealm();

// Create a binding that calls a function inside the ShadowRealm:
const shadowFunction = shadow.evaluate( &amp;#39;( value ) =&amp;gt; globalThis.someValue = value );

// ...and call our wrapped function using that binding:
shadowFunction( &amp;quot;Hello from the ShadowRealm!&amp;quot; );

// Executing this function in the host realm doesn&amp;#39;t _change_ anything here, of course:
console.log( globalThis.someValue );
// Result: undefined

// But we can grab the result from the ShadowRealm:
const shadowValue = shadow.evaluate( &amp;#39;globalThis.someValue&amp;#39; );

// And use it here in the host realm:
console.log( shadowValue );
// Result: Hello from the ShadowRealm!&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Infinite disposable cleanrooms! Pocket dimensions where we can execute whatever code we want, without fear of that code interfering with the scope of any other ShadowRealm &lt;em&gt;or&lt;/em&gt; the outer realm — the “light realm,” if you will.&lt;/p&gt;&lt;p&gt;Now, some of you — especially those of you who’ve been doing this since the early days of JavaScript — have probably been recoiling at these examples. You’d be forgiven for thinking that ShadowRealm API is just goth &lt;code&gt;eval&lt;/code&gt;, and you wouldn’t be strictly wrong: apart from running in the context of a ShadowRealm, what you’ve seen so far here are basically indirect calls to &lt;code&gt;eval&lt;/code&gt; — even subject to the same &lt;code&gt;unsafe-eval&lt;/code&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy&quot;&gt;Content Security Policy&lt;/a&gt; rule.&lt;/p&gt;&lt;p&gt;Fear not for your workflows, however: while these are &lt;em&gt;illustrative&lt;/em&gt; examples, this isn’t the only way to put ShadowRealms to use. The proposal includes an &lt;code&gt;importValue&lt;/code&gt; method on the ShadowRealm object’s prototype, which allows you to dynamically import modules, then capture and work with exported values and functions:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// spookycode.js
export function greeting() {
 return &amp;quot;Hello from the ShadowRealm!&amp;quot;;
}&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;async function shadowGreeter() {
  // I INVOKE THE DARK POWER OF THE SHADOWREALM- ahem. Sorry.
  const shadow = new ShadowRealm();

  /* 
  * `importValue` returns a promise that resolves with the value of the function 
  * specified in the second argument: 
  */
  const shadowGreet = await shadow.importValue( &amp;quot;./spookycode.js&amp;quot;, &amp;quot;greeting&amp;quot; );

  // Call our wrapped function, annnnd...
  shadowGreet();
}

shadowGreeter();
// Result: Hello from the ShadowRealm!&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The shadow hasn’t fallen yet&lt;/h3&gt;&lt;p&gt;I’m pleased to say that you’ve now seen the &lt;em&gt;entirety&lt;/em&gt; of the proposed ShadowRealms API, at this point. The proposal includes only those the two methods you’ve seen here — &lt;code&gt;evaluate&lt;/code&gt; and &lt;code&gt;importValue&lt;/code&gt; — both means of &lt;del&gt;banishing&lt;/del&gt; evaluating code in the context of a ShadowRealm instance while still &lt;em&gt;executing&lt;/em&gt; that code in the context of the host realm’s thread.&lt;/p&gt;&lt;p&gt;Again, though: none of this can be put to use just yet. The proposed specification is currently at &lt;a href=&quot;https://tc39.es/process-document/&quot;&gt;Stage 2.7&lt;/a&gt; — “approved in principle and undergoing validation,” meaning that it’s only likely to change as a result of feedback from tests and trial implementations in browsers, if at all. You’re playing a move ahead by reading this. When this proposal reaches Stage 3 and we start to see implementations in browsers, you’ll be ready to try it out for yourself. Nay, more than ready — at such time as the awesome power of the ShadowRealm is loosed upon the web, you shall stand at the ready to command its dark and fearsome majjycks! &lt;em&gt;The very realm upon which our code stands shall quake, as&lt;/em&gt;— okay, okay, sorry. Look, I can’t help it! I mean, “&lt;em&gt;ShadowRealm&lt;/em&gt;,” for cryin’ out loud.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>CSS &amp; vertical rhythm for text, images, and tables</title>
<link>https://vincent.bernat.ch/en/blog/2026-css-vertical-rhythm</link>
<enclosure type="image/jpeg" length="0" url="https://d2pzklc15kok91.cloudfront.net/images/covers/en/blog/2026-css-vertical-rhythm.04ed9a5f31bf66.jpg"></enclosure>
<guid isPermaLink="false">6Q87v4IbmaOGhV-Emx0YsInbDmGOOvhiXhzEWA==</guid>
<pubDate>Tue, 12 May 2026 09:01:47 +0000</pubDate>
<description>Hold vertical rhythm with the rlh CSS unit for text, a bit of JavaScript for images, some CSS wizardry for tables.</description>
<content:encoded>&lt;p&gt;Vertical rhythm aligns lines to a consistent spacing cadence down the page. It
creates a predictable flow for the eye to follow. Thanks to the &lt;code&gt;rlh&lt;/code&gt; CSS unit,
vertical rhythm is now easier to implement for text.&lt;sup&gt;1&lt;/sup&gt; But illustrations
and tables can disrupt the layout. The amateur typographer in me wants to follow
Bringhurst’s wisdom:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;Headings, subheads, block quotations, footnotes, illustrations, captions and
other intrusions into the text create syncopations and variations against the
base rhythm of regularly leaded lines. These variations can and should add
life to the page, but the main text should also return after each variation
precisely on beat and in phase.&lt;/p&gt;
&lt;p&gt;― &lt;em&gt;Robert Bringhurst&lt;/em&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Elements_of_Typographic_Style&quot;&gt;The Elements of Typographic Style&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;h1&gt;Text#&lt;/h1&gt;&lt;p&gt;Three factors govern vertical rhythm: &lt;strong&gt;font size&lt;/strong&gt;, &lt;strong&gt;line height&lt;/strong&gt; and
&lt;strong&gt;margin or padding&lt;/strong&gt;. Let’s set our baseline with an 18-pixel font and a 1.5
line height:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/css-values-4/&quot;&gt;CSS Values and Units Module Level 4&lt;/a&gt; defines the &lt;code&gt;rlh&lt;/code&gt; unit, equal to the
computed line height of the root element. All browsers support it &lt;a href=&quot;https://webstatus.dev/features/rlh&quot;&gt;since
2023&lt;/a&gt;.&lt;sup&gt;2&lt;/sup&gt; Use it to insert vertical spaces or to fix the line height
when altering font size:&lt;sup&gt;3&lt;/sup&gt;&lt;/p&gt;&lt;p&gt;We can check the result by overlaying a grid&lt;sup&gt;4&lt;/sup&gt; on the content:&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;span&gt;&lt;img src=&quot;https://d2pzklc15kok91.cloudfront.net/images/vertical-rhythm/text@1x.17b2e8512acd85.png&quot; alt=&quot;Screenshot of my website with a grid as an overlay and each line of text
fitting on the grid&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;figcaption&gt;Using CSS &lt;code&gt;rlh&lt;/code&gt; unit to set vertical space works well for text. You can display the grid using &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;G&lt;/kbd&gt;.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;If a child element uses a font with taller intrinsic metrics, it may stretch
the line’s box beyond the configured line height.&lt;sup&gt;5&lt;/sup&gt; A workaround
is to reduce the line height to 1. The glyphs overflow but don’t push the line
taller.&lt;/p&gt;&lt;h1&gt;Responsive images#&lt;/h1&gt;&lt;p&gt;Responsive images are difficult to align on the grid because we don’t know their
height. &lt;a href=&quot;https://www.w3.org/TR/css-rhythm-1/&quot;&gt;CSS Rhythmic Sizing Module Level 1&lt;/a&gt; introduces the &lt;code&gt;block-step&lt;/code&gt;
property to adjust the height of an element to a multiple of a step unit. But
most browsers don’t support it yet.&lt;/p&gt;&lt;p&gt;With JavaScript, we can add padding around the image so it does not disturb
the vertical rhythm:&lt;/p&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;const&lt;/span&gt; &lt;span&gt;targets&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;.lf-media-outer&amp;quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;
&lt;span&gt;const&lt;/span&gt; &lt;span&gt;adjust&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;height&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt; &lt;span&gt;rlh&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;parseFloat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;getComputedStyle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;documentElement&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;lineHeight&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;
&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt; &lt;span&gt;padding&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ceil&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;height&lt;/span&gt; &lt;span&gt;/&lt;/span&gt; &lt;span&gt;rlh&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;rlh&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;height&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;  &lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;padding&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;padding&lt;/span&gt; &lt;span&gt;/&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;px 0`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;};&lt;/span&gt;

&lt;span&gt;targets&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;adjust&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;el&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;clientHeight&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;figure&gt;&lt;div&gt;&lt;span&gt;&lt;img src=&quot;https://d2pzklc15kok91.cloudfront.net/images/vertical-rhythm/images@1x.08d84fe73e020f.png&quot; alt=&quot;Screenshot of my website with a grid as an overlay and an image not breaking
the vertical rhythm. Additional padding is visible before and after the image.
The height of the image with padding is
216.&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;figcaption&gt;The image is snapped to the grid thanks to the additional padding computed with JavaScript. 216 is divisible by 27, our line height in this example.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;As the image is responsive, its height can change. We need to wrap a resize
observer around the &lt;code&gt;adjust()&lt;/code&gt; function:&lt;/p&gt;&lt;h1&gt;Tables#&lt;/h1&gt;&lt;p&gt;Table cells could set &lt;code&gt;1rlh&lt;/code&gt; as their height but they would feel constricted.
Using &lt;code&gt;2rlh&lt;/code&gt; wastes too much space. Instead, we use &lt;a href=&quot;https://markboulton.co.uk/journal/incremental-leading/&quot;&gt;incremental leading&lt;/a&gt;: we
align one in every five lines.&lt;/p&gt;&lt;p&gt;To align the elements after the table, we need to add some padding. We can
either reuse the JavaScript code from images or use a few lines of CSS that
count the regular rows and compute the missing vertical padding:&lt;/p&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;table&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tbody&lt;/span&gt; &lt;span&gt;tr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;last-child&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;{&lt;/span&gt; &lt;span&gt;padding-bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;rlh&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;table&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tbody&lt;/span&gt; &lt;span&gt;tr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5n&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;last-child&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; &lt;span&gt;padding-bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0.8&lt;/span&gt;&lt;span&gt;rlh&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;table&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tbody&lt;/span&gt; &lt;span&gt;tr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5n&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;last-child&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; &lt;span&gt;padding-bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;rlh&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;table&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tbody&lt;/span&gt; &lt;span&gt;tr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5n&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;last-child&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; &lt;span&gt;padding-bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0&lt;/span&gt; &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;table&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tbody&lt;/span&gt; &lt;span&gt;tr&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5n&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;last-child&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; &lt;span&gt;padding-bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;rlh&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A header cell has twice the padding of a regular cell. With two regular rows,
the total padding is 2×2×0.2+2×0.4=1.6. We need to add &lt;code&gt;0.4rlh&lt;/code&gt; to reach
&lt;code&gt;2rlh&lt;/code&gt; of extra vertical padding across the table.&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;span&gt;&lt;img src=&quot;https://d2pzklc15kok91.cloudfront.net/images/vertical-rhythm/tables@1x.56e678195427d0.png&quot; alt=&quot;Screenshot of my website with a grid as an overlay and a table following the
vertical rhythm. Additional padding is visible after the table. The height of
the table with padding is 405.&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;figcaption&gt;One line out of five is aligned to the grid. Additional padding is added after the table to not break the vertical rhythm. 405 is divisible by 27, our line height in this example.&lt;/figcaption&gt;&lt;/figure&gt;&lt;hr/&gt;&lt;p&gt;None of this is necessary. But once you start looking, you can’t unsee it. Until
browsers implement &lt;a href=&quot;https://www.w3.org/TR/css-rhythm-1/&quot;&gt;CSS Rhythmic Sizing&lt;/a&gt;, a
bit of CSS wizardry and a touch of JavaScript is enough to pull it off. The main
text now returns after each intrusion “precisely on beat and in phase.” 🎼&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Browser OpenTelemetry without the compromises: How to stay vendor-neutral and still get full RUM</title>
<link>https://embrace.io/blog/browser-opentelemetry-without-compromises/</link>
<guid isPermaLink="false">BQSMaTHV9hbr0blDmoRm4cAgks1kuuEVKp9Xug==</guid>
<pubDate>Tue, 12 May 2026 00:01:43 +0000</pubDate>
<description>A real-world React SPA setup surfaced a question about browser OTel. The answer points to a better way to instrument the web.</description>
<content:encoded>&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h2&gt;A Reddit question that stayed with me&lt;/h2&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;div&gt;&lt;img src=&quot;https://embrace.io/wp-content/uploads/2026/05/260423_BrowserOTelRedditPost_1200x627-1-scaled.png&quot; alt=&quot;Embrace, OpenTelemetry and Reddit Graphic&quot; title=&quot;260423_BrowserOTelRedditPost_1200x627 (1)&quot;/&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;A developer posted in r/Observability with a setup that’s increasingly common: React SPA on NGINX, OpenTelemetry JS SDK already wired up, telemetry flowing through a custom reverse proxy to Splunk. Their client wanted to add Grafana Cloud as a second destination. They considered Grafana Faro because it handles CORS natively and is purpose-built for browser RUM.&lt;/p&gt;&lt;p&gt;But the client had been burned by a proprietary SDK before and had a hard requirement: Pure OpenTelemetry only, nothing vendor-specific.&lt;/p&gt;&lt;p&gt;I answered the post because this is a problem I deal with directly in my work on the OTel Browser project. The client made the right call. The entire point of OpenTelemetry is that your instrumentation code shouldn’t be tightly coupled to any one backend. Proprietary SDKs on day one means enormous switching costs later and a vendor that has leverage over you.&lt;/p&gt;&lt;p&gt;But there’s a problem. Almost no observability backend supports CORS natively on their OTLP ingestion endpoint. They’re all built for server-side collectors. Browsers aren’t part of the design.&lt;/p&gt;&lt;p&gt;It’s a problem that deserves a longer treatment than a Reddit comment. Here’s what else I want to talk about.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;The browser problem&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;OTLP ingestion was designed for server-side collectors. The assumed flow is: Your app emits telemetry, an OTel Collector picks it up, the Collector forwards it to whatever backend you’re using. That works on a server, but a browser can’t run a Collector. And when a browser tries to make a direct cross-origin request to a remote OTLP endpoint, it gets blocked. Every time.&lt;/p&gt;&lt;p&gt;No backend accepts it directly. Not Splunk Cloud. Not Grafana Cloud. Not Datadog. Not Elastic. Not even Jaeger, which has had an open GitHub issue for CORS support since 2023.&lt;/p&gt;&lt;p&gt;The developer in that thread had figured out the right workaround: Deploy a collector that supports CORS as a gateway. In their case, Grafana Alloy sitting in their client’s Azure environment, configured to accept browser traffic and fan out to both Splunk and Grafana Cloud.&lt;/p&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;otelcol.receiver.otlp &amp;quot;default&amp;quot; {
  http {
    endpoint = &amp;quot;0.0.0.0:4318&amp;quot;
    cors {
      allowed_origins = [&amp;quot;https://your-frontend-origin.com&amp;quot;]
      allowed_headers = [&amp;quot;*&amp;quot;]
      max_age = 7200
    }
  }
  output {
    traces = [otelcol.exporter.otlphttp.grafana.input]
    metrics = [otelcol.exporter.otlphttp.grafana.input]
    logs = [otelcol.exporter.otlphttp.grafana.input]
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the standard pattern right now. It works, it’s composable, and it keeps the vendor-neutral constraint intact.&lt;/p&gt;&lt;p&gt;But it doesn’t answer the harder question.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;The real question: Was it worth it?&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Buried at the end of the post was this:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;“For those who’ve done browser OTEL without Faro — was it worth it vs just using a RUM tool, or did you end up missing the session tracking and web vitals?”&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This cuts to the core of where browser OTel is right now.&lt;/p&gt;&lt;p&gt;Raw &lt;code&gt;@opentelemetry/sdk-trace-web&lt;/code&gt; gives you traces and logs. That’s useful. But traces alone don’t tell you how your users are experiencing your site. You also need session tracking, Core Web Vitals capture, click instrumentation, error handling, and user journey visibility. And if you go raw OTel, you’re building all of that yourself.&lt;/p&gt;&lt;p&gt;These aren’t small gaps you can paper over in a sprint. Session tracking alone is a real project. Core Web Vitals (LCP, INP, CLS) are what Google uses to rank your site. They have direct business impact. Not capturing them isn’t an option.&lt;/p&gt;&lt;p&gt;So in practice: A team commits to pure OTel, gets traces working, then realizes they need all this other stuff. They pull in a RUM SDK to fill the gaps. Suddenly they’ve reintroduced a vendor dependency and the vendor-neutral goal is gone.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;What Embrace &amp;amp; the OTel community are building&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;There’s active work on a dedicated OpenTelemetry Browser SDK and instrumentations that address exactly this gap: Session tracking, web vitals, click events, error capture, all built on OTel foundations and fully portable. We pushed for a dedicated browser repo separate from Node because browser instrumentation has different enough constraints that it needed its own home. The ongoing effort involves migrating browser-specific instrumentation out of the Node JS core and contrib repos into that home.&lt;/p&gt;&lt;p&gt;At Embrace, two of us are maintainers of OpenTelemetry Browser. I work on it directly. It’s the right long-term answer for the ecosystem, but it’s not done yet. If you need to ship something in the next quarter, “it’s coming” isn’t a plan.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-browser/issues&quot;&gt;If you want to follow the work, file issues, or contribute, come join us&lt;/a&gt;! We’re looking for contributors and your feedback helps shape what gets prioritized.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;What I recommend today&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;The Embrace Web SDK is open source and built natively on OpenTelemetry. Not wrapped around it, not bolted on top of it. OTel primitives are the foundation and it behaves the way you’d expect an OTel-first tool to behave.&lt;/p&gt;&lt;p&gt;Two things matter most for a setup like the one in that thread:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The Embrace backend accepts browser traffic directly. You can send from the browser to Embrace without needing a proxy. If you want the gateway for fanning out to multiple backends, that still works. But you’re no longer forced to maintain one just to get browser telemetry to land somewhere.&lt;/li&gt;&lt;li&gt;It accepts custom exporters. You can configure the SDK to send to your Alloy pipeline, to Splunk, to Embrace, or to all three at once. When you want to change backends, you change the exporter config, not your instrumentation code. That’s what vendor neutrality actually looks like: Not just a philosophical commitment, but a concrete architectural property.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;For the developer in that thread, that means: Drop the Embrace SDK into the React SPA, keep the Alloy gateway if you want the multi-destination fan-out, and you’ve got full RUM (session tracking, Core Web Vitals, error handling, user journey visibility) without asking the client to compromise on their vendor-neutral stance. The instrumentation stays clean and the backend choices stay yours. Here’s an example sending telemetry to Embrace and a third-party like Grafana Cloud.&lt;/p&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;import { initSDK } from &amp;#39;@embrace-io/web-sdk&amp;#39;;
import { OTLPLogExporter } from &amp;#39;@opentelemetry/exporter-logs-otlp-http&amp;#39;;
import { OTLPTraceExporter } from &amp;#39;@opentelemetry/exporter-trace-otlp-http&amp;#39;;

initSDK({
  appID: &amp;quot;YOUR_EMBRACE_APP_ID&amp;quot;, // Send to Embrace
  appVersion: &amp;quot;YOUR_APP_VERSION&amp;quot;,
  spanExporters: [
    // Send to third-party
    new OTLPTraceExporter({
      url: &amp;#39;https://example.com/endpoint/for/traces&amp;#39;,
      headers: {
        &amp;#39;Authorization&amp;#39;: &amp;#39;Basic TOKEN&amp;#39;
      }
    }),
  ],
  logExporters: [
    // Send to third-party
    new OTLPLogExporter({
      url: &amp;#39;https://example.com/endpoint/for/logs&amp;#39;,
      headers: {
        &amp;#39;Authorization&amp;#39;: &amp;#39;Basic TOKEN&amp;#39;
      }
    }),
  ],
  defaultInstrumentationConfig: {
    network: {
      ignoreUrls: [&amp;#39;https://example.com/endpoint/for/traces&amp;#39;, &amp;#39;https://example.com/endpoint/for/logs&amp;#39;],
    },
  },
});&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/embrace-io/embrace-web-sdk&quot;&gt;SDK source&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://embrace.io/docs/web/advanced-features/opentelemetry-export/#setup-custom-exporters&quot;&gt;Custom exporter setup &lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;You don’t actually have to compromise&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;I’ve spent my career in web performance and observability, and something still bothers me about how the industry treats browser observability. Most of the big observability platforms were built for servers. When browser monitoring became something teams wanted, those platforms added it as a feature, not a focus. A checkbox in a sales conversation.&lt;/p&gt;&lt;p&gt;In practice that means sampled data and high-level metrics. Not the full picture of what individual users are experiencing. Full-fidelity browser observability, where you can trace a slow LCP back to the third-party script that caused it or connect a frontend error to the backend trace it triggered, has historically been either a specialized tool or a very expensive add-on. Datadog’s full-fidelity offering would cost most organizations around 10x what they’re paying for sampled data. Most teams just accept the tradeoff.&lt;/p&gt;&lt;p&gt;Embrace was built differently. Newer architecture, full fidelity from the start, at a cost that actually works. And because it’s built on OTel natively, it slots into whatever observability stack you’re already running: Honeycomb, Chronosphere, Grafana, or anything else.&lt;/p&gt;&lt;p&gt;Embrace also offers synthetic testing through its recent acquisition of Speedcurve, the best in the business. This isn’t something you can realistically build yourself. Synthetic testing catches performance regressions before your users do, and combining it with real user monitoring from the same platform gives you the full picture: What your users are experiencing now and what they’ll experience after your next deploy.&lt;/p&gt;&lt;p&gt;The developer on Reddit was asking a practical question about CORS headers. But underneath it was a more important question: Can you have both vendor neutrality and a real understanding of your users’ web experience? You can.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;Where to start&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;If any of this resonates with what you’re building or evaluating:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://github.com/embrace-io/embrace-web-sdk&quot;&gt;The Embrace Web SDK&lt;/a&gt;&lt;/strong&gt;: Open source, OTel-native, full RUM out of the box.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://embrace.io/docs/web/advanced-features/opentelemetry-export&quot;&gt;Custom exporter setup:&lt;/a&gt;&lt;/strong&gt; Route to your existing pipeline, add Embrace, or both.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://github.com/open-telemetry/opentelemetry-browser&quot;&gt;The OTel Browser project&lt;/a&gt;&lt;/strong&gt;: The community effort to build the vendor-neutral future of browser instrumentation. We’re looking for contributors.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You shouldn’t have to choose between the right architectural decision and actually understanding your users. With the right tooling, you don’t have to.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/Observability/comments/1rx2uza/how_do_you_handle_browser_otel_telemetry_when/https://www.reddit.com/r/Observability/comments/1rx2uza/how_do_you_handle_browser_otel_telemetry_when/&quot;&gt;You can read the original post from r/Observability&lt;/a&gt;. I’ll gladly continue the conversation if anyone has thoughts or questions.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;img src=&quot;https://embrace.io/wp-content/themes/embraceio/library/images/blog-logo.svg&quot; alt=&quot;Embrace&quot; title=&quot;&quot;/&gt;&lt;span&gt;Deliver incredible mobile experiences with Embrace.&lt;/span&gt;&lt;p&gt;Get started today with 1 million free user sessions.&lt;/p&gt;&lt;a href=&quot;https://dash.embrace.io/signup/&quot;&gt;Get started free&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/div&gt;</content:encoded>
</item>
</channel>
</rss>
