<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>x2q</title>
    <subtitle>x2q is a personal blog with notes on software, Linux, networking, payments, home labs, cooking, and the odd side project. Online since 2010.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://www.x2q.net/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://www.x2q.net"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-04-20T00:00:00+00:00</updated>
    <id>https://www.x2q.net/atom.xml</id>
    <entry xml:lang="en">
        <title>elpriser.org — Danish hourly electricity prices</title>
        <published>2026-04-20T00:00:00+00:00</published>
        <updated>2026-04-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/elpriser-org-danish-electricity-prices/"/>
        <id>https://www.x2q.net/post/elpriser-org-danish-electricity-prices/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/elpriser-org-danish-electricity-prices/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elpriser.org&quot;&gt;elpriser.org&lt;&#x2F;a&gt; shows the &lt;strong&gt;real, all-in hourly price of electricity in Denmark&lt;&#x2F;strong&gt; — not just the spot price. It combines the Nord Pool spot price, your local grid tariff (&lt;code&gt;netselskab&lt;&#x2F;code&gt;), Energinet’s system and transmission tariffs, the state electricity tax (&lt;code&gt;elafgift&lt;&#x2F;code&gt;), and 25% VAT. Data comes from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.energidataservice.dk&#x2F;&quot;&gt;Energi Data Service&lt;&#x2F;a&gt; and updates daily after Nord Pool publishes the next 24 hours around 13:00. DK1 (Vestdanmark) and DK2 (Østdanmark). Danish-language, free, no login.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-does-danish-electricity-actually-cost&quot;&gt;What does Danish electricity actually cost?&lt;&#x2F;h2&gt;
&lt;p&gt;The “spot price” you see on most energy dashboards is only one component. The price you actually pay per kWh is made up of six parts:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Spot price&lt;&#x2F;strong&gt; — set hourly on the Nord Pool wholesale market for the following day.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Grid tariff (nettarif)&lt;&#x2F;strong&gt; — paid to your local grid operator (&lt;code&gt;netselskab&lt;&#x2F;code&gt;). Varies by company and by time-of-day (off-peak, shoulder, peak).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;System tariff (systemtarif)&lt;&#x2F;strong&gt; — paid to Energinet, the Danish TSO.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Transmission tariff (transmissionstarif)&lt;&#x2F;strong&gt; — also paid to Energinet.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Electricity tax (elafgift)&lt;&#x2F;strong&gt; — a state tax on consumption.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;VAT (moms)&lt;&#x2F;strong&gt; — 25% applied on top of everything else.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Most price sites show the first number. A few show one or two more. None that I could find showed all six, hour by hour, for both price zones, with the correct grid tariff automatically picked for the user’s &lt;code&gt;netselskab&lt;&#x2F;code&gt;. That’s what elpriser.org does.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-two-price-zones-dk1-and-dk2&quot;&gt;The two price zones: DK1 and DK2&lt;&#x2F;h2&gt;
&lt;p&gt;Denmark is split into two electricity price zones by geography — the Great Belt separates them — and they often have different prices at the same hour:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DK1 — Vestdanmark.&lt;&#x2F;strong&gt; Jylland and Fyn. More wind-dominated.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;DK2 — Østdanmark.&lt;&#x2F;strong&gt; Sjælland, Lolland, Falster, Møn, Bornholm. More interconnected with Sweden (SE4) and Germany.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When the wind blows hard in Jylland, DK1 can be cheap while DK2 is pricey. When an interconnector is down, the gap widens further. elpriser.org shows both zones side by side.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;which-netselskab-are-you-on&quot;&gt;Which netselskab are you on?&lt;&#x2F;h2&gt;
&lt;p&gt;You cannot choose your &lt;code&gt;netselskab&lt;&#x2F;code&gt; — it depends on where you live — but the grid tariff they charge is a significant chunk of your bill, and it varies by &lt;strong&gt;15–25 øre&#x2F;kWh at peak&lt;&#x2F;strong&gt; between the cheapest and the most expensive, which works out to &lt;strong&gt;~500–1,000 kr&#x2F;year&lt;&#x2F;strong&gt; for a typical household.&lt;&#x2F;p&gt;
&lt;p&gt;Rough ranking of peak (spidslast) tariffs as of 2025:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Zone&lt;&#x2F;th&gt;&lt;th&gt;Netselskab&lt;&#x2F;th&gt;&lt;th&gt;Peak tariff (øre&#x2F;kWh)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;DK1&lt;&#x2F;td&gt;&lt;td&gt;RAH Net&lt;&#x2F;td&gt;&lt;td&gt;~33&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK1&lt;&#x2F;td&gt;&lt;td&gt;Trefor&lt;&#x2F;td&gt;&lt;td&gt;~37&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK1&lt;&#x2F;td&gt;&lt;td&gt;N1&lt;&#x2F;td&gt;&lt;td&gt;~46&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK2&lt;&#x2F;td&gt;&lt;td&gt;Cerius&lt;&#x2F;td&gt;&lt;td&gt;~47&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DK2&lt;&#x2F;td&gt;&lt;td&gt;Radius&lt;&#x2F;td&gt;&lt;td&gt;~65&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Smaller operators (Konstant, Nord Energi, Vores Elnet, El-net Kongerslev, and others) each have their own tariffs. elpriser.org lets you pick yours; the all-in hourly price adjusts accordingly.&lt;&#x2F;p&gt;
&lt;p&gt;You can’t switch &lt;code&gt;netselskab&lt;&#x2F;code&gt;, but you &lt;strong&gt;can&lt;&#x2F;strong&gt; shift consumption — laundry, dishwasher, EV charging, heat-pump heating — into the low-tariff hours. The hourly chart exists for exactly that.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;historical-extremes&quot;&gt;Historical extremes&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Record high:&lt;&#x2F;strong&gt; 16.69 kr&#x2F;kWh (spot price, excl. VAT) in DK2, at 19:00 on 5 September 2022, during the European energy crisis.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Record low:&lt;&#x2F;strong&gt; −2.76 kr&#x2F;kWh in DK1, at 14:00 on 2 July 2023, when wind production overshot demand.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Negative prices are not a bug: when production is high and demand is low, producers pay consumers to absorb the surplus. Modern price-aware heat pumps and EV chargers can lean into this.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-elpriser-org-is-built&quot;&gt;How elpriser.org is built&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Static HTML&lt;&#x2F;strong&gt;, regenerated once per day after Nord Pool’s next-day publication around 13:00 CET.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Data source&lt;&#x2F;strong&gt;: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.energidataservice.dk&#x2F;&quot;&gt;Energi Data Service&lt;&#x2F;a&gt;, the open-data service from Energinet. No API keys required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Structured data&lt;&#x2F;strong&gt;: &lt;code&gt;schema.org&#x2F;WebApplication&lt;&#x2F;code&gt; and &lt;code&gt;FAQPage&lt;&#x2F;code&gt; are emitted on every page so Google can surface the prices directly in search results and AI overviews.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hosting&lt;&#x2F;strong&gt;: a small origin behind Cloudflare. Cacheable for the full day; purged on the daily rebuild.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Language&lt;&#x2F;strong&gt;: Danish only. The domain name should have been a hint.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The whole thing is a single-purpose tool: no tracking, no login, no newsletter popover, no “10 ways to save on your electricity bill” listicles.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;how-often-does-elpriser-org-update&quot;&gt;How often does elpriser.org update?&lt;&#x2F;h3&gt;
&lt;p&gt;Once a day, after the Nord Pool day-ahead auction publishes next-day prices around 13:00 CET. Intraday adjustments are not shown; most consumers are billed against the day-ahead price anyway.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-the-price-shown-with-or-without-vat&quot;&gt;Is the price shown with or without VAT?&lt;&#x2F;h3&gt;
&lt;p&gt;With. The headline number on elpriser.org is the all-in price per kWh: spot + tariffs + tax + 25% VAT. You can toggle to see the breakdown.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-are-prices-in-dk1-and-dk2-different&quot;&gt;Why are prices in DK1 and DK2 different?&lt;&#x2F;h3&gt;
&lt;p&gt;Denmark has two physically separated electricity areas joined by the Great Belt interconnector. When the interconnector is fully used or wind production differs sharply between east and west, the two zones clear at different Nord Pool prices.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-i-see-the-breakdown-of-each-component&quot;&gt;Can I see the breakdown of each component?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes. Every hourly cell can be expanded to show spot price, each tariff component, elafgift, and VAT.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;do-i-really-pay-negative-prices-when-the-wholesale-price-goes-negative&quot;&gt;Do I really pay negative prices when the wholesale price goes negative?&lt;&#x2F;h3&gt;
&lt;p&gt;It depends on your contract. Pure spot-price contracts pass the raw Nord Pool price through — tariffs and tax still apply, so the all-in price can go slightly negative or just very low. Fixed-price contracts don’t.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-danish-only&quot;&gt;Why Danish only?&lt;&#x2F;h3&gt;
&lt;p&gt;The data, the regulatory rules, the &lt;code&gt;netselskab&lt;&#x2F;code&gt; structure, and the target audience are all Danish. An English version would be translating context, not content.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;where-does-the-data-come-from&quot;&gt;Where does the data come from?&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.energidataservice.dk&#x2F;en&#x2F;dataset&#x2F;elspotprices&quot;&gt;Energi Data Service&lt;&#x2F;a&gt; (Nord Pool spot) and Energinet’s tariff data. Both are official public data sets from the Danish TSO.&lt;&#x2F;p&gt;
&lt;p&gt;Small, focused, no login, no tracking. The way consumer energy tools should look.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>winniemethmann.com — from WordPress to Astro</title>
        <published>2026-04-19T00:00:00+00:00</published>
        <updated>2026-04-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/winniemethmann-com-astro-portfolio/"/>
        <id>https://www.x2q.net/post/winniemethmann-com-astro-portfolio/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/winniemethmann-com-astro-portfolio/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;winniemethmann.com&quot;&gt;winniemethmann.com&lt;&#x2F;a&gt; is the portfolio of a Danish food photographer and recipe developer. It was a WordPress site for a decade. I rebuilt it on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;astro.build&quot;&gt;Astro&lt;&#x2F;a&gt;, using &lt;strong&gt;content collections&lt;&#x2F;strong&gt; (typed Markdown) instead of a CMS, &lt;strong&gt;Sharp + AVIF&lt;&#x2F;strong&gt; for the image pipeline, and &lt;strong&gt;Astro’s i18n routing&lt;&#x2F;strong&gt; (Danish default, English at &lt;code&gt;&#x2F;en&#x2F;&lt;&#x2F;code&gt;). Build time: ~30 seconds. Output size: ~70% smaller. No more admin panel, no more plugin updates, no more bot-probed login endpoint.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-move-off-wordpress&quot;&gt;Why move off WordPress&lt;&#x2F;h2&gt;
&lt;p&gt;For a portfolio that changes a few times a month, WordPress was doing too much work:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PHP runtime and MySQL&lt;&#x2F;strong&gt; for a site that is functionally static.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Dozens of plugins&lt;&#x2F;strong&gt; for image galleries, contact forms, SEO, caching — each with its own update cadence and security disclosures.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A half-forked theme&lt;&#x2F;strong&gt; that had accumulated patches nobody remembered why.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;An admin login endpoint&lt;&#x2F;strong&gt; that was probed several thousand times a day by bots.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Regular caching headaches&lt;&#x2F;strong&gt; every time the CDN and the plugins disagreed.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Concretely, dropping WordPress meant:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No admin panel to keep patched.&lt;&#x2F;strong&gt; WordPress core releases, plugin updates, theme updates — gone.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No database.&lt;&#x2F;strong&gt; Content lives as Markdown in the repo.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No login surface for bots to probe.&lt;&#x2F;strong&gt; There’s no &lt;code&gt;&#x2F;wp-admin&#x2F;&lt;&#x2F;code&gt; anymore.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;~10× faster page loads&lt;&#x2F;strong&gt; with no caching layer required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;~70% smaller total build size&lt;&#x2F;strong&gt;, swapping hand-exported JPEGs for AVIF at sensible &lt;code&gt;srcset&lt;&#x2F;code&gt; breakpoints.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The tradeoff is that non-technical editing goes away. In practice that did not matter: the site owner was happier editing Markdown than fighting the WordPress block editor.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-astro&quot;&gt;Why Astro&lt;&#x2F;h2&gt;
&lt;p&gt;I considered Hugo, 11ty, Next.js static export, and SvelteKit. Astro won on three specific points:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Content collections.&lt;&#x2F;strong&gt; A typed, schema-checked way to describe a portfolio project as a directory of photos plus some front-matter. Build fails loudly if anything is malformed. No CMS required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;&amp;lt;Image&amp;gt;&lt;&#x2F;code&gt; component.&lt;&#x2F;strong&gt; Astro’s built-in image pipeline handles AVIF + JPEG fallback + &lt;code&gt;srcset&lt;&#x2F;code&gt; + &lt;code&gt;width&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;height&lt;&#x2F;code&gt; attributes with a one-liner. Sharp is the engine under the hood.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Islands architecture, not relevant here.&lt;&#x2F;strong&gt; The site has no interactive components, so it ships basically zero JavaScript.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;content-collections-not-a-cms&quot;&gt;Content collections, not a CMS&lt;&#x2F;h2&gt;
&lt;p&gt;Each portfolio project is a directory under &lt;code&gt;src&#x2F;content&#x2F;portfolio&#x2F;&lt;&#x2F;code&gt; with a front-matter schema:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;src&#x2F;content&#x2F;portfolio&#x2F;
├── 2024-cookbook-editorial&#x2F;
│   ├── index.mdx
│   ├── cover.jpg
│   ├── 01.jpg, 02.jpg, …
└── 2023-spring-catalogue&#x2F;
    ├── index.mdx
    ├── cover.jpg
    └── 01.jpg …
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;index.mdx&lt;&#x2F;code&gt; front-matter declares the project title, category, year, and cover image. Astro enforces the schema at build time via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.astro.build&#x2F;en&#x2F;guides&#x2F;content-collections&#x2F;&quot;&gt;&lt;code&gt;defineCollection&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; with a Zod schema. If a project is missing a cover image or has a bad category, the build fails.&lt;&#x2F;p&gt;
&lt;p&gt;Categories used: &lt;strong&gt;food photography, recipe development, interior &amp;amp; garden styling, editorial, cookbooks, and fashion&lt;&#x2F;strong&gt;. Adding a new project is &lt;code&gt;mkdir&lt;&#x2F;code&gt; + &lt;code&gt;cp *.jpg&lt;&#x2F;code&gt; + a short YAML front-matter block. No admin UI, no database migration, no cache to invalidate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;image-pipeline-sharp-avif&quot;&gt;Image pipeline: Sharp + AVIF&lt;&#x2F;h2&gt;
&lt;p&gt;Food photography lives or dies on image quality. Astro’s &lt;code&gt;&amp;lt;Image&amp;gt;&lt;&#x2F;code&gt; component, powered by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sharp.pixelplumbing.com&#x2F;&quot;&gt;Sharp&lt;&#x2F;a&gt;, generates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AVIF&lt;&#x2F;strong&gt; as the primary format. Roughly all modern browsers support it now (see &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;caniuse.com&#x2F;avif&quot;&gt;caniuse&lt;&#x2F;a&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JPEG&lt;&#x2F;strong&gt; fallback with matching &lt;code&gt;srcset&lt;&#x2F;code&gt; breakpoints (320, 640, 960, 1280, 1920 px).&lt;&#x2F;li&gt;
&lt;li&gt;Explicit &lt;code&gt;width&lt;&#x2F;code&gt; and &lt;code&gt;height&lt;&#x2F;code&gt; attributes, so there is zero cumulative layout shift while images load.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;loading=&quot;lazy&quot;&lt;&#x2F;code&gt; for below-the-fold images and &lt;code&gt;fetchpriority=&quot;high&quot;&lt;&#x2F;code&gt; for the hero.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Numbers&lt;&#x2F;strong&gt;: a typical portfolio page went from ~4.8 MB of hand-exported JPEGs to ~1.4 MB of AVIF — about a 70% reduction — with no visible quality loss at typical display sizes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;i18n-danish-default-english-at-en&quot;&gt;i18n — Danish default, English at &#x2F;en&#x2F;&lt;&#x2F;h2&gt;
&lt;p&gt;Astro’s i18n routing sits in &lt;code&gt;astro.config.mjs&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;js&quot;&gt;export default defineConfig({
  i18n: {
    defaultLocale: &amp;quot;da&amp;quot;,
    locales: [&amp;quot;da&amp;quot;, &amp;quot;en&amp;quot;],
    routing: { prefixDefaultLocale: false },
  },
});
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That gives:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&#x2F;&lt;&#x2F;code&gt; for Danish (the default, no prefix).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&#x2F;en&#x2F;&lt;&#x2F;code&gt; for English.&lt;&#x2F;li&gt;
&lt;li&gt;Each content entry declares its language via its collection and filename.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link rel=&quot;alternate&quot; hreflang&amp;gt;&lt;&#x2F;code&gt; and the sitemap are generated from the same source.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Posts and portfolio entries that lack a translation simply don’t appear in the other locale’s routing — they don’t 404 to a wrong-language page.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deployment-and-build&quot;&gt;Deployment and build&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Output&lt;&#x2F;strong&gt;: fully static, deployed as plain files behind Cloudflare.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Build time&lt;&#x2F;strong&gt;: ~30 seconds on a cold cache, ~6 seconds incrementally.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No server, no database, no runtime cost.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;measurable-outcomes&quot;&gt;Measurable outcomes&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;&#x2F;th&gt;&lt;th&gt;WordPress (before)&lt;&#x2F;th&gt;&lt;th&gt;Astro (after)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Build &#x2F; deploy time&lt;&#x2F;td&gt;&lt;td&gt;n&#x2F;a (instant publish)&lt;&#x2F;td&gt;&lt;td&gt;~30s cold, ~6s warm&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Typical page size (portfolio page)&lt;&#x2F;td&gt;&lt;td&gt;~4.8 MB&lt;&#x2F;td&gt;&lt;td&gt;~1.4 MB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Largest Contentful Paint&lt;&#x2F;td&gt;&lt;td&gt;~2.8 s&lt;&#x2F;td&gt;&lt;td&gt;~0.9 s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Time to Interactive&lt;&#x2F;td&gt;&lt;td&gt;~3.5 s&lt;&#x2F;td&gt;&lt;td&gt;~1.1 s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;JS shipped&lt;&#x2F;td&gt;&lt;td&gt;~220 KB&lt;&#x2F;td&gt;&lt;td&gt;~0 KB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Plugins to keep patched&lt;&#x2F;td&gt;&lt;td&gt;14&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;can-a-non-technical-owner-still-edit-the-site&quot;&gt;Can a non-technical owner still edit the site?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, for copy. Editing Markdown in a GitHub web editor is, in practice, easier than the WordPress block editor. For new portfolio projects, the workflow is: drag images into a folder, write a short front-matter block, commit. If that’s too technical, a one-page admin (Decap CMS &#x2F; Sveltia CMS &#x2F; Keystatic) can be bolted on.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-about-seo-after-the-migration&quot;&gt;What about SEO after the migration?&lt;&#x2F;h3&gt;
&lt;p&gt;URLs were kept as-is wherever possible. Missing old paths redirect via Cloudflare rules. The sitemap is regenerated on every build, and structured data (&lt;code&gt;Person&lt;&#x2F;code&gt;, &lt;code&gt;ImageGallery&lt;&#x2F;code&gt;, &lt;code&gt;Article&lt;&#x2F;code&gt;) is emitted per page.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-astro-and-not-hugo&quot;&gt;Why Astro and not Hugo?&lt;&#x2F;h3&gt;
&lt;p&gt;Hugo is faster and simpler for pure blogging, but Astro’s typed content collections and first-class &lt;code&gt;&amp;lt;Image&amp;gt;&lt;&#x2F;code&gt; component won this case. For &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.x2q.net&quot;&gt;x2q.net&lt;&#x2F;a&gt; itself, I later went with Zola — for a portfolio with a heavy image pipeline, Astro remained the better fit.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-do-images-stay-organised&quot;&gt;How do images stay organised?&lt;&#x2F;h3&gt;
&lt;p&gt;Each portfolio project owns its own folder. The Git repo is the CMS. &lt;code&gt;git log&lt;&#x2F;code&gt; tells you when an image was added and why.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-the-build-deterministic&quot;&gt;Is the build deterministic?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes. Given the same inputs, the output is byte-for-byte identical. Sharp is pinned in &lt;code&gt;package.json&lt;&#x2F;code&gt;; so is Astro. CI runs on Node LTS.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re on a WordPress site that’s doing far less than its infrastructure suggests, Astro is worth the afternoon it takes to try.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>apextowww.com — a free apex-to-www redirect service</title>
        <published>2026-04-18T00:00:00+00:00</published>
        <updated>2026-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.x2q.net/post/apextowww-com-apex-to-www-redirect/"/>
        <id>https://www.x2q.net/post/apextowww-com-apex-to-www-redirect/</id>
        
        <content type="html" xml:base="https://www.x2q.net/post/apextowww-com-apex-to-www-redirect/">&lt;p&gt;&lt;strong&gt;TL;DR —&lt;&#x2F;strong&gt; DNS does not allow &lt;code&gt;CNAME&lt;&#x2F;code&gt; records at the zone apex (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc1034#section-3.6.2&quot;&gt;RFC 1034 §3.6.2&lt;&#x2F;a&gt;). That’s why hosts like Netlify, Vercel, Cloudflare Pages, Firebase, and Heroku ask you to configure &lt;code&gt;www.example.com&lt;&#x2F;code&gt; with a &lt;code&gt;CNAME&lt;&#x2F;code&gt; and leave &lt;code&gt;example.com&lt;&#x2F;code&gt; (the apex) as a problem. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;apextowww.com&quot;&gt;apextowww.com&lt;&#x2F;a&gt; solves it: point two &lt;code&gt;A&lt;&#x2F;code&gt; records and two &lt;code&gt;AAAA&lt;&#x2F;code&gt; records at the service, and it issues a Let’s Encrypt certificate for your apex and &lt;code&gt;301&lt;&#x2F;code&gt;-redirects every request to &lt;code&gt;https:&#x2F;&#x2F;www.yourdomain.tld&#x2F;&lt;&#x2F;code&gt;, preserving path and query string. Free, no signup, no account.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-you-can-t-put-a-cname-on-the-apex&quot;&gt;Why you can’t put a CNAME on the apex&lt;&#x2F;h2&gt;
&lt;p&gt;The DNS spec is unambiguous: a zone apex (the “bare” domain like &lt;code&gt;example.com&lt;&#x2F;code&gt;) must carry an &lt;code&gt;SOA&lt;&#x2F;code&gt; record and usually &lt;code&gt;NS&lt;&#x2F;code&gt; records. &lt;code&gt;CNAME&lt;&#x2F;code&gt; is defined as an alias that must be the only record at a name, and it is forbidden to coexist with the &lt;code&gt;SOA&lt;&#x2F;code&gt; and &lt;code&gt;NS&lt;&#x2F;code&gt; records that the apex is required to have. That’s why you can &lt;code&gt;CNAME www.example.com → mysite.netlify.app&lt;&#x2F;code&gt; but you &lt;strong&gt;cannot&lt;&#x2F;strong&gt; &lt;code&gt;CNAME example.com → mysite.netlify.app&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Workarounds exist, and all of them are a little awkward:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ALIAS &#x2F; ANAME records.&lt;&#x2F;strong&gt; Proprietary to each DNS provider (Cloudflare, Route 53, DNSimple, NS1). They work by resolving the target behind the scenes and returning an &lt;code&gt;A&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;AAAA&lt;&#x2F;code&gt;. Fine if your DNS provider supports it; useless if it doesn’t.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Flattening at the provider level.&lt;&#x2F;strong&gt; Cloudflare’s “CNAME flattening” is this, done automatically for you.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Your own always-on VPS doing &lt;code&gt;301 → www&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt; This is what I was doing for multiple domains, and it’s both over-engineered and a small maintenance tax.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;apextowww is the fourth option: somebody else’s always-on redirector, managed for you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-apextowww-does&quot;&gt;What apextowww does&lt;&#x2F;h2&gt;
&lt;p&gt;Exactly one thing: a &lt;code&gt;301 Moved Permanently&lt;&#x2F;code&gt; from &lt;code&gt;https:&#x2F;&#x2F;yourdomain.tld&#x2F;anything?x=y&lt;&#x2F;code&gt; to &lt;code&gt;https:&#x2F;&#x2F;www.yourdomain.tld&#x2F;anything?x=y&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TLS&lt;&#x2F;strong&gt; is issued automatically on first request via the Let’s Encrypt HTTP-01 challenge. No ACME client to run yourself.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IPv4 + IPv6&lt;&#x2F;strong&gt; on dual-stack by design. Both apex records are required.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;1.1, HTTP&#x2F;2, and HTTP&#x2F;3&lt;&#x2F;strong&gt; are all served. The redirect itself is trivially small, so protocol version matters less, but it helps the first-paint story when the redirect is on the critical path.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Path and query string&lt;&#x2F;strong&gt; are preserved, so deep links keep working.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No signup, no login, no account.&lt;&#x2F;strong&gt; If your DNS is pointed correctly, it just works.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;how-to-set-it-up&quot;&gt;How to set it up&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;apextowww.com&quot;&gt;apextowww.com&lt;&#x2F;a&gt; and copy the current IP addresses (two IPv4, two IPv6).&lt;&#x2F;li&gt;
&lt;li&gt;In your DNS provider, set &lt;code&gt;A&lt;&#x2F;code&gt; records on your apex pointing at the two IPv4 addresses. Remove any existing &lt;code&gt;A&lt;&#x2F;code&gt; record at the apex.&lt;&#x2F;li&gt;
&lt;li&gt;Set &lt;code&gt;AAAA&lt;&#x2F;code&gt; records on your apex pointing at the two IPv6 addresses.&lt;&#x2F;li&gt;
&lt;li&gt;Make sure &lt;code&gt;www.yourdomain.tld&lt;&#x2F;code&gt; still points at your real host (CNAME to Netlify&#x2F;Vercel&#x2F;Pages&#x2F;etc).&lt;&#x2F;li&gt;
&lt;li&gt;Wait for DNS to propagate. Visit &lt;code&gt;http:&#x2F;&#x2F;yourdomain.tld&#x2F;&lt;&#x2F;code&gt; — it should 301 to &lt;code&gt;https:&#x2F;&#x2F;www.yourdomain.tld&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The apextowww site has per-platform walkthroughs at &lt;code&gt;&#x2F;netlify-apex-domain-redirect&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;vercel-apex-domain-redirect&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;cloudflare-pages-apex-redirect&#x2F;&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;firebase-hosting-apex-redirect&#x2F;&lt;&#x2F;code&gt;, and &lt;code&gt;&#x2F;heroku-apex-domain-redirect&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;stack&quot;&gt;Stack&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hetzner ARM64&lt;&#x2F;strong&gt; servers, for cheap, low-power compute. ARM64 is ~30% cheaper per vCPU at Hetzner than x86 and runs the redirector fine.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Caddy-style automatic TLS&lt;&#x2F;strong&gt; with &lt;strong&gt;Let’s Encrypt&lt;&#x2F;strong&gt; HTTP-01 challenges.&lt;&#x2F;li&gt;
&lt;li&gt;Dual-stack &lt;strong&gt;IPv4 + IPv6&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;1.1, HTTP&#x2F;2, HTTP&#x2F;3&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Public &lt;strong&gt;static marketing site&lt;&#x2F;strong&gt; on Cloudflare Pages, with per-platform guides in separate URL paths so they each rank independently on Google for “netlify apex redirect”, “vercel apex domain”, etc.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;can-i-use-apextowww-with-cloudflare-dns&quot;&gt;Can I use apextowww with Cloudflare DNS?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes, but you’ll want Cloudflare’s proxy (orange cloud) &lt;strong&gt;off&lt;&#x2F;strong&gt; for the apex &lt;code&gt;A&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;AAAA&lt;&#x2F;code&gt; records. If the proxy is on, Cloudflare intercepts the request and Let’s Encrypt’s HTTP-01 challenge won’t reach apextowww. Once the certificate is issued and renewed, you don’t need to toggle anything manually; just keep it off.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;does-apextowww-support-wildcards-or-redirecting-subdomains-other-than-www&quot;&gt;Does apextowww support wildcards or redirecting subdomains other than www?&lt;&#x2F;h3&gt;
&lt;p&gt;No. The service only redirects the apex to &lt;code&gt;www.&lt;&#x2F;code&gt;. Everything else stays on your real host.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-happens-if-the-apextowww-ips-change&quot;&gt;What happens if the apextowww IPs change?&lt;&#x2F;h3&gt;
&lt;p&gt;You’ll need to update your DNS &lt;code&gt;A&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;AAAA&lt;&#x2F;code&gt; records. The operator publishes current IPs on the homepage and gives advance notice for changes. For a personal domain this is fine; for anything mission-critical, run your own redirector.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-there-a-rate-limit&quot;&gt;Is there a rate limit?&lt;&#x2F;h3&gt;
&lt;p&gt;There’s no published hard limit, but this is a free community service. If you’re serving millions of apex redirects a day, self-host.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-not-just-use-cloudflare-s-free-plan&quot;&gt;Why not just use Cloudflare’s free plan?&lt;&#x2F;h3&gt;
&lt;p&gt;Cloudflare’s CNAME flattening at the apex is a perfectly reasonable alternative if your whole DNS is on Cloudflare. apextowww is useful when your DNS isn’t on Cloudflare, or when you want a host-agnostic redirector that works identically across domains.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;is-it-really-free&quot;&gt;Is it really free?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes. The marginal cost per redirect on ARM64 is negligible. No ads, no tracking beyond basic logs, no upsell.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-it-exists&quot;&gt;Why it exists&lt;&#x2F;h2&gt;
&lt;p&gt;Hosting platforms optimise for their own onboarding, not for the DNS truisms that trip up every new user. Sending people to a stranger’s VPS felt worse than running one myself. Now my own apex domains (including &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.x2q.net&quot;&gt;x2q.net&lt;&#x2F;a&gt;) point at apextowww, which means I could tear down the last little redirector VPS I still had running for historical reasons.&lt;&#x2F;p&gt;
&lt;p&gt;It’s the kind of project that isn’t interesting until you need it, and then it’s the only thing you need.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
