<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Callum</title>
    <description>The latest articles on DEV Community by Callum (@wrux).</description>
    <link>https://dev.to/wrux</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F590391%2F1e4d2c63-1e73-4f4d-9599-4ea592389892.jpg</url>
      <title>DEV Community: Callum</title>
      <link>https://dev.to/wrux</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wrux"/>
    <language>en</language>
    <item>
      <title>It's time we stop over-engineering website frontends</title>
      <dc:creator>Callum</dc:creator>
      <pubDate>Fri, 21 Aug 2026 17:40:21 +0000</pubDate>
      <link>https://dev.to/wrux/its-time-we-stop-over-engineering-website-frontends-3g4d</link>
      <guid>https://dev.to/wrux/its-time-we-stop-over-engineering-website-frontends-3g4d</guid>
      <description>&lt;p&gt;I thought I would share a rant regarding JavaScript usage on modern websites. &lt;/p&gt;

&lt;p&gt;To clarify, this discussion focuses primarily on content sites rather than complex web applications requiring heavy client side interactivity. &lt;/p&gt;

&lt;p&gt;In recent years, frontend developers frequently chased shiny new tools without pausing to evaluate necessity. Building websites now routinely involves an endless list of dependencies, a &lt;code&gt;node_modules&lt;/code&gt; directory large enough to form a black hole, and countless supply chain vulnerabilities. Standard websites frequently consume over 500 megabytes of RAM in a single browser tab. We have thoroughly over-engineered the web. &lt;/p&gt;

&lt;p&gt;Why did we start using JavaScript and React to generate simple HTML in the first place? Perhaps because it felt convenient at the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend Libraries
&lt;/h2&gt;

&lt;p&gt;Frontend libraries and metaframeworks empower developers to build anything from a static marketing page to a massive enterprise application. However, we often fell into the trap of applying heavy over-engineering to simple web projects. Frontend libraries are crucial for large applications, but completely unnecessary for smaller websites. &lt;/p&gt;

&lt;p&gt;I used to love NextJS, but much of its overhead felt redundant for standard publishing needs. It simplified building fast experiences, server-generating JavaScript, and leveraging features like page link prefetching on hover. Yet we have successfully generated HTML on the server for over thirty years. Did we really need to reinvent that foundation?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am Building
&lt;/h2&gt;

&lt;p&gt;In recent projects, I took a step back to earlier days of web development before everything became overly complicated. I discovered &lt;a href="https://data-star.dev/" rel="noopener noreferrer"&gt;Datastar&lt;/a&gt; a while ago, and it fundamentally changed my workflow. Think of it as using the server to generate UI elements, utilizing server-sent events, client-side signals, and lightweight interactivity. I now build websites containing perhaps 10-20kb of JavaScript in total while requiring no compiler step. The performance is great, the API remains simple, and dependency tracking risks are gone since there are no external packages to manage.&lt;/p&gt;

&lt;p&gt;My transition toward lightweight websites began when AstroJS first came on the scene. The philosophy of shipping zero JavaScript by default for static HTML was refreshing to see again. Fast forward a couple of years, and I needed to build an interactive website with server-driven content and chose Astro. To solve this, I built a &lt;a href="https://github.com/wrux/astro-datastar" rel="noopener noreferrer"&gt;Datastar plugin for AstroJS&lt;/a&gt;, which proved really useful and I published it on NPM. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foaasc9cv06ewfnt6lu98.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foaasc9cv06ewfnt6lu98.png" alt="Datastar + Astro integration examples screenshot" width="2264" height="1316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Several examples are live om Datastar Astro project: &lt;a href="https://astro-datastar.wrux-6a1.workers.dev/examples/" rel="noopener noreferrer"&gt;https://astro-datastar.wrux-6a1.workers.dev/examples/&lt;/a&gt;. Feel free to inspect the network tab while testing them and to get an idea of how Datastar works. &lt;/p&gt;

&lt;p&gt;I am currently build a &lt;a href="https://banburyshire.com/" rel="noopener noreferrer"&gt;project&lt;/a&gt; that uses a Cloudflare worker running Rust and WebAssembly to generate pages. It operates as a streamlined backend setup that reads from a CMS, handles caching, and outputs pages very efficiently. Datastar has a native Rust integration that performs exceptionally fast inside workers compiling to WebAssembly. This architectural shift allowed me to drop Vercel, which frequently hit resource limits, and migrate to Cloudflare for free up to 100,000 monthly page views while getting much better TTFB on webpages.&lt;/p&gt;

&lt;p&gt;Consequently, I can now deliver an interactive website that generates pages in under 10ms while sending only 16 kilobytes of JavaScript down to the client. I also dropped Tailwind entirely and built a custom design system, though that is a topic for another rant. With Brotli compression enabled, the total asset weight sent to the client is nearly negligible. &lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Use More Browser APIs
&lt;/h2&gt;

&lt;p&gt;The web landscape evolved significantly, and modern browsers improve continuously. It is time to return to building simple websites by relying more heavily on native web standards. &lt;/p&gt;

&lt;p&gt;Implementing lazy-loading images once required custom JavaScript solutions, but today it is handled natively via a single &lt;code&gt;loading="lazy"&lt;/code&gt; attribute. Creating accessible dialog elements used to involve complex workarounds, yet we now possess the native &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element and a straightforward API. &lt;/p&gt;

&lt;p&gt;NextJS and Astro gained popularity partly by offering useful utility tooling, such as pre-loading subsequent pages to create an instant perceived performance boost on click. This capability has already arrived in browsers as a native standard via the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API" rel="noopener noreferrer"&gt;Speculation Rules API&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Below is an example code snippet implementing the Speculation Rules API to instruct the browser which links to prefetch on hover. This is supported in &lt;a href="https://caniuse.com/mdn-api_request_destination_speculationrules" rel="noopener noreferrer"&gt;Chromium based browsers&lt;/a&gt;, highlighting how native browser implementations are replacing features once locked inside proprietary metaframeworks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speculationrules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prefetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;where&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;and&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href_matches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href_matches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eagerness&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moderate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Minimising Analytics Overhead
&lt;/h2&gt;

&lt;p&gt;Every developer knows the frustration of putting immense effort into building a lean content website with pristine performance metrics, only for marketing teams to then request 500kb of tracking scripts.&lt;/p&gt;

&lt;p&gt;Managing this tension is always challenging. I have experimented with &lt;a href="https://plausible.io/" rel="noopener noreferrer"&gt;Plausible&lt;/a&gt; and similar privacy-focused alternatives, though marketing managers sometimes find them restricted in certain reporting dimensions.&lt;/p&gt;

&lt;p&gt;Many stakeholders still expect traditional platforms like Google Analytics by default. If anyone has practical insights on how to maintain robust tracking without bloating client-side JavaScript payloads, I would love to hear your approach.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>rust</category>
      <category>htmx</category>
    </item>
    <item>
      <title>Creating Favicons for Light and Dark Mode UIs</title>
      <dc:creator>Callum</dc:creator>
      <pubDate>Wed, 07 Jul 2021 06:29:24 +0000</pubDate>
      <link>https://dev.to/wrux/creating-favicons-for-light-and-dark-mode-uis-3o74</link>
      <guid>https://dev.to/wrux/creating-favicons-for-light-and-dark-mode-uis-3o74</guid>
      <description>&lt;p&gt;Dark mode UIs are becoming increasingly popular. Often the legibility of a favicon is not great in light or dark mode, so a dynamic solution is needed to display correctly in both modes. &lt;/p&gt;

&lt;p&gt;Luckily, SVG favicons provide the functionality we need. If you are not using SVG favicons on your site yet, then go and change them now. You can drop those 20+ PNG icons in favour of a single SVG. &lt;/p&gt;

&lt;p&gt;So here's the thing, we can use CSS in SVGs, meaning you can also use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme" rel="noopener noreferrer"&gt;prefers-color-scheme&lt;/a&gt; media query to adjust the SVG. &lt;/p&gt;

&lt;p&gt;Here's an example icon with some CSS to set the default path fill colour to black. The media query then changes the fill colour to white if the user has dark mode enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 429.056 429.056"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nt"&gt;path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M413.696 182.272h-21.504v-68.096c-.512-8.192-7.168-14.848-15.36-15.36H229.888V4.608h-30.72v94.208H52.224c-8.192.512-14.848 7.168-15.36 15.36v68.096H15.36c-8.704 0-15.36 6.656-15.36 15.36v101.376c0 8.704 6.656 15.36 15.36 15.36h21.504v94.72c.512 8.192 7.168 14.848 15.36 15.36h324.608c8.192-.512 14.848-7.168 15.36-15.36v-94.72h21.504c8.704 0 15.36-6.656 15.36-15.36V197.632c0-8.704-6.656-15.36-15.36-15.36zm-313.344 36.352c0-24.576 19.968-44.544 44.544-44.544s44.544 19.968 44.544 44.544-19.968 44.544-44.544 44.544-44.544-19.968-44.544-44.544zm181.76 133.632H146.944v-30.72h135.168v30.72zm46.592-133.632c0 24.576-19.968 44.544-44.544 44.544-25.088 0-45.056-19.968-45.568-44.544 0-25.088 19.968-45.056 44.544-45.568 24.576-.512 45.056 19.968 45.568 44.544v1.024z"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just need to link your new &lt;code&gt;icon.svg&lt;/code&gt; file in the document head:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/assets/icon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the outcome. You might need to reload the page when switching between light/dark mode UI. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feykqkp9g7t011je89wu4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feykqkp9g7t011je89wu4.png" alt="Dark mode favicon" width="506" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gu7fo5bbiyd3ielmjbs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gu7fo5bbiyd3ielmjbs.png" alt="Light mode favicon" width="516" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
    </item>
    <item>
      <title>Emoji Favicons Are a Thing Now 🤯🚀</title>
      <dc:creator>Callum</dc:creator>
      <pubDate>Tue, 06 Jul 2021 11:49:43 +0000</pubDate>
      <link>https://dev.to/wrux/emoji-favicons-2cm6</link>
      <guid>https://dev.to/wrux/emoji-favicons-2cm6</guid>
      <description>&lt;p&gt;Emojis are everywhere nowadays. I created &lt;a href="https://perpetual.pizza" rel="noopener noreferrer"&gt;perpetual.pizza&lt;/a&gt; many years ago now and tend to update it every so often. I never liked the old pizza favicon and decided to try replacing it with an emoji. &lt;/p&gt;

&lt;p&gt;SVG favicons are now widely supported and I have been experimenting with the possibilities recently (light/dark mode favicons are awesome). I ended up finding an elegant solution for creating an emoji favicon. &lt;/p&gt;

&lt;p&gt;It turns out that you can use emojis inside of SVG text and &lt;br&gt;
use relative units to position and size the emoji correctly. This method also displays the emoji in the format the user is familiar with on their operating system.&lt;/p&gt;

&lt;p&gt;Here's the markup for my icon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 100 100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"0.9em"&lt;/span&gt; &lt;span class="na"&gt;font-size=&lt;/span&gt;&lt;span class="s"&gt;"90"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;🍕&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added it into the document head like a regular SVG icon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/assets/icon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the outcome: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff037eplydhuafzbotnlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff037eplydhuafzbotnlp.png" alt="Pizza emoji favicon in tab" width="446" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
  </channel>
</rss>
