<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>RSS Feed for webdev - James Stone&apos;s Blog</title><description>Blog posts tagged with webdev</description><link>https://jamesst.one/</link><item><title>A no backend future</title><link>https://jamesst.one/posts/no-backend-future/</link><guid isPermaLink="true">https://jamesst.one/posts/no-backend-future/</guid><description>&lt;![CDATA[&lt;p&gt;A typical web tech stack is a frontend , backend and db&lt;/p&gt;
&lt;p&gt;My prediction is the future for many ( not all)  apps will be an LLM dynamically built frontend, using something like this
&lt;a href=&quot;https://tambo.co/blog/posts/introducing-tambo-generative-ui&quot;&gt;https://tambo.co/blog/posts/introducing-tambo-generative-ui&lt;/a&gt; or MCP UI&lt;/p&gt;
&lt;p&gt;Plus a DB that has strict permissions (think postgres RLS) and validation (check constraints etc)&lt;/p&gt;]]&gt;</description><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate></item><item><title>Web Transport</title><link>https://jamesst.one/posts/web-transport/</link><guid isPermaLink="true">https://jamesst.one/posts/web-transport/</guid><description>There is a new (to me) way to communicate between the browser and server - Web Transport. [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)

| Technology               | Intended Use                                                        | Direction                                | Other Limitations                                | MDN                                                                        |
|--------------------------|---------------------------------------------------------------------|------------------------------------------|--------------------------------------------------|----------------------------------------------------------------------------|
| Web Transport            | Real-time, Bidirectional, can have sub streams                      | Server ↔ Client                          | HTTP/3 only. Secure context. No Safari (for now) | [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)   |
| WebSocket                | Real-time, Bidirectional communication over a single TCP connection | Server ↔ Client                          |                                                  | [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)          |
| SSE (Server-Sent Events) | One way stream of data from server                                  | Server → Client                          |                                                  | [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) |
| Web Push                 | Low frequncy updates, push notifications.                           | Server → Browser&apos;s Server → Browser      |                                                  | [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)           |
| WebRTC                   | Real-time communication (audio, video, data) between browsers       | Client A ↔ Client B ( could be a server) |                                                  | [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API)         |

Web Transport fills a similar niche as Web Sockets. But it has a neat potential advantage over it. It allows multiple streams to be created on the same connection - crucially, each stream does not stall the connection. This solves a problem with Web Sockets where each message is sequentially processed. So if you send a large message followed by lots of small messages, the client has to wait for the large message.

{
/*
```mermaid
    graph TD
        subgraph &quot;WebSocket: is a single stream&quot;
            direction TB
            SA[LARGE\n\nMessage]
            SA --&gt; SB[Small Message 1]
            SB --&gt; SC[Small Message 2]
        end
      style SA fill:#ff9,stroke:#333,stroke-width:2px
```
*/
}
In Web Transport, multiple independent streams can be created, allowing simultaneous processing of different message types.

{
/*
```mermaid
  graph TD
      subgraph &quot;Web Transport: Multiple streams, one connection&quot;

          subgraph &quot;Stream 1&quot;
               direction TB
               MA[LARGE\n\nMessage]
          end

          subgraph &quot;Stream 2&quot;
              direction TB
              MB[Small Message 1]
              MB --&gt; MC[Small Message 2]
          end

      end

      style MA fill:#ff9,stroke:#333,stroke-width:2px

```
*/
}
# example use case

Picture a collaborative document editor where multiple users are working together:

- Small cursor position updates (high frequency, need low latency)
- Text changes/edits (medium-sized messages)
- Document metadata (larger messages like version history)

With Web Transport, all these can flow independently on the same connection, so cursor movements remain responsive even when large document changes are being processed. Unlike WebSockets, one user&apos;s large edit won&apos;t stall another user&apos;s cursor position updates.

A prior solution was to open multiple WebSocket connections, one for each type of message. But this has a large overhead.

---

Also of note is the Background Synchronization API [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Background_Synchronization_API) although that is not really supported and is **not** triggerable from the server.</description><pubDate>Wed, 04 Feb 2026 00:00:00 GMT</pubDate></item><item><title>Scroll padding top</title><link>https://jamesst.one/posts/scroll-padding-top/</link><guid isPermaLink="true">https://jamesst.one/posts/scroll-padding-top/</guid><description>&lt;![CDATA[&lt;p&gt;When you have a &lt;strong&gt;sticky&lt;/strong&gt; header, clicking an anchor link can cause the target element to be hidden behind the header. TIL &lt;code&gt;scroll-padding-top&lt;/code&gt; solves this by adding an offset to the scroll position.&lt;/p&gt;
&lt;p&gt;I think a screenshot best describes the problem:&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;#x22;src&amp;#x22;:&amp;#x22;../post_assets/sticky.png&amp;#x22;,&amp;#x22;alt&amp;#x22;:&amp;#x22;Sticky Header covering linked anchor&amp;#x22;,&amp;#x22;index&amp;#x22;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;here I have made set the header background’s opacity to 50% so you can see when you click the anchor the header obscures the target element.&lt;/p&gt;
&lt;p&gt;On this site, I use it in the &lt;code&gt;Nav.astro&lt;/code&gt; component to ensure that the header height is accounted for when scrolling to a fragment:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;css&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;:root&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  --header-height&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  scroll-padding-top&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;--header-height&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;nav&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  height&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;--header-height&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  position&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;sticky&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  top&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures that when a user navigates to an anchor, the top of the content is aligned with the bottom of the sticky navigation bar rather than being obscured by it.&lt;/p&gt;
&lt;p&gt;eg:&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;#x22;src&amp;#x22;:&amp;#x22;../post_assets/sticky-fixed.png&amp;#x22;,&amp;#x22;alt&amp;#x22;:&amp;#x22;Sticky Header not covering linked anchor&amp;#x22;,&amp;#x22;index&amp;#x22;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;Live demo: My &lt;a href=&quot;/blog#2026&quot;&gt;2026 posts&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;More details: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scroll-padding-top&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scroll-padding-top&lt;/a&gt;&lt;/p&gt;]]&gt;</description><pubDate>Wed, 28 Jan 2026 00:00:00 GMT</pubDate></item><item><title>Why Multi page apps are exciting again</title><link>https://jamesst.one/posts/mpa-comeback/</link><guid isPermaLink="true">https://jamesst.one/posts/mpa-comeback/</guid><description>&lt;![CDATA[&lt;p&gt;One thing that really excites me in the browser world right now is the renewed focus on &lt;strong&gt;multi-page applications (MPAs)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For a long time, &lt;strong&gt;single-page applications (SPAs)&lt;/strong&gt; felt like the only viable way to achieve certain features and performance characteristics. If you wanted fast navigation or polished page transitions, an SPA was basically the &lt;del&gt;default&lt;/del&gt;only choice.&lt;/p&gt;
&lt;p&gt;Some of the classic advantages of SPAs included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fast page loading &lt;em&gt;after&lt;/em&gt; the initial load&lt;/li&gt;
&lt;li&gt;smooth page transitions and navigation effects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the interesting thing is that these features are no longer exclusive to SPAs.&lt;/p&gt;
&lt;h2 id=&quot;new-browser-features-are-leveling-the-playing-field&quot;&gt;New browser features are leveling the playing field&lt;/h2&gt;
&lt;h3 id=&quot;compression&quot;&gt;Compression&lt;/h3&gt;
&lt;p&gt;Soon, browsers will be able to use &lt;em&gt;super smart compression&lt;/em&gt; based on pages you’ve already loaded. Instead of re-downloading large chunks of repeated HTML (headers, navigation, layout, etc.), servers can send much smaller diffs.&lt;/p&gt;
&lt;p&gt;If you want to dig deeper into this, check out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Compression_Dictionary_Transport&quot;&gt;Compression Dictionary Transport – HTTP | MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.debugbear.com/blog/shared-compression-dictionaries&quot;&gt;The Ultimate Guide to Shared Compression Dictionaries | DebugBear&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This directly chips away at one of the historical weaknesses of MPAs: having to reload the entire document on every navigation.&lt;/p&gt;
&lt;h3 id=&quot;native-prefetching-and-prerendering&quot;&gt;Native prefetching and prerendering&lt;/h3&gt;
&lt;p&gt;Another area where SPAs used to dominate was &lt;strong&gt;preloading&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Previously, only SPAs could easily preload the next page using JavaScript. For example, on link hover you might start loading the next page’s data, then instantly render it on click and refresh it in the background.&lt;/p&gt;
&lt;p&gt;Now, browsers offer a native alternative via the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API&quot;&gt;Speculation Rules API | MDN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Instead of custom JavaScript logic, you can declaratively tell the browser what kinds of pages should be prefetched or prerendered:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;speculationrules&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &quot;prerender&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &quot;where&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          &quot;and&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            { &quot;href_matches&quot;: &quot;/*&quot; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            { &quot;not&quot;: { &quot;href_matches&quot;: &quot;/logout&quot; } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            { &quot;not&quot;: { &quot;href_matches&quot;: &quot;/*\\?*(^|&amp;#x26;)add-to-cart=*&quot; } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            { &quot;not&quot;: { &quot;selector_matches&quot;: &quot;.no-prerender&quot; } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            { &quot;not&quot;: { &quot;selector_matches&quot;: &quot;[rel~=nofollow]&quot; } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &quot;prefetch&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &quot;urls&quot;: [&quot;next.html&quot;, &quot;next2.html&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &quot;requires&quot;: [&quot;anonymous-client-ip-when-cross-origin&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &quot;referrer_policy&quot;: &quot;no-referrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives MPAs many of the same preloading capabilities that previously required an SPA and a client-side router.&lt;/p&gt;
&lt;h2 id=&quot;page-transitions&quot;&gt;Page transitions&lt;/h2&gt;
&lt;p&gt;Finally, although I think page transitions can be a gimmick, for a long time smooth transitions were another feature that only an SPA could provide.&lt;/p&gt;
&lt;p&gt;That’s changing with the introduction of &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/view-transitions/&quot;&gt;View Transitions&lt;/a&gt;.
With &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/view-transitions/cross-document&quot;&gt;cross document&lt;/a&gt;
view transitions, MPAs can now animate between full page navigations in a way that previously required client-side rendering.&lt;/p&gt;]]&gt;</description><pubDate>Sat, 17 Jan 2026 00:00:00 GMT</pubDate></item></channel></rss>