{
  "title": "xmit dev team's blog",
  "home_page_url": "https://xmit.dev",
  "feed_url": "https://xmit.dev/feed.json",
  "version": "https://jsonfeed.org/version/1.1",
  "icon": "https://xmit.dev/img/icon.svg",
  "favicon": "https://xmit.dev/img/icon.svg",
  "items": [
    {
        "id": "https://xmit.dev/posts/origin/",
        "url": "https://xmit.dev/posts/origin/",
        "title": "The origin story",
        "date_published": "2023-11-26T00:00:00.000Z",
        "content_html": "<p>Host static sites. Sounds trivial, right?</p>\n<p>Here are details worth considering, whether you host on your own servers or rely on a platform like the one we're\nbuilding.</p>\n<h2>Security: full HTTPS, HSTS, headers galore</h2>\n<p>Securing all traffic is a no-brainer.</p>\n<p>All HTTP traffic is redirected to HTTPS, and we\ninclude <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security\">HSTS</a> headers everywhere.</p>\n<p>We go a bit further,\nwith <code>referrer-policy: no-referrer</code>, <code>x-content-type-options: nosniff</code>, <code>x-frame-options: SAMEORIGIN</code> out of the box.</p>\n<h2>Admin: 2 clicks to sign up or sign in, everything in real time</h2>\n<p>We've been there: the latest launch is broken, a rollback is in order.</p>\n<p>We've made it as fast and simple as possible, and as with any launch, it's effective across our infrastructure in\nmilliseconds.</p>\n<p>And to make sure wires don't get crossed, the entire admin console is fully reactive.</p>\n<h2>Uploads: only what's changed</h2>\n<p><code>xmit</code> is smart enough to only upload files that have changed. It's open source (0BSD license), so you can inspect the\ndetails. In short, it generates a manifest of all file hashes, uploads it only if it's new (checked by hash),\nthen uploads only files whose hashes are new.</p>\n<h2>Safe launches: atomicity &amp; fallthrough</h2>\n<p>With atomic launches, it's all or nothing, and within the milliseconds of propagation delays of our coordination\nsystem (<a href=\"https://etcd.io/\">etcd</a>). A client won't see HTML referring to a new JS/CSS/asset until it's been made\navailable on its connection, avoiding a common source of 404s.</p>\n<p>To fully avoid 404s, previous launches also need to be excluded slowly: servers need to fall through, so a client which\nloaded HTML just before a launch, which referred to JS/CSS/assets not included in said launch, still gets enough chances\nto load them (today, over 5 launches and 10 minutes, such that removing a resource propagates consistently within an\nshort window).</p>\n<h2>xmit subdomains: every upload is visible to your team</h2>\n<p>Every upload can be accessed through a dedicated xmit subdomain, letting you vet it before launch if you didn't enable\nlaunch on upload. Those subdomains are only accessible to your team. They stick around, so it's easy to check what was\nonline at any point in recent history.</p>\n<h2>Protocols: HTTP/1.1, HTTP/2, &amp; H3</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/HTTP/2\">HTTP/2</a> was a leap forward. <a href=\"https://en.wikipedia.org/wiki/QUIC\">QUIC</a> is\nanother.</p>\n<p>We support both, announced through DNS HTTPS records and Alt-Svc headers.</p>\n<h2><code>accept-ranges: bytes</code></h2>\n<p>Resume downloads, play videos in Safari, and more. We support <code>range</code> requests and advertise it to clients.</p>\n<h2>Compression: <code>gzip</code> whenever worthwhile</h2>\n<p>Rather than hardcode a list of extensions or MIME types worthy of compression,\nwe give it a go on everything, and use the compressed version when requested and smaller.</p>\n<h2>Caching: <code>ETag</code> &amp; <code>if-none-match</code></h2>\n<p>na\nOur servers only rely on content-addressible caching. Browsers cache URLs.</p>\n<p>We do not introduce any propagation delays in CDNs, proxies, or browser caches other than through headers you control.</p>\n<p>But if the browser saw a resource already, no reason to transfer it again. That's\nwhere <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag\"><code>ETag</code></a> comes in.\nWe always report one, and return <code>304 Not Modified</code> if it appears in the <code>if-none-match</code> header received from clients.</p>\n<h2><code>www</code> subdomain</h2>\n<p>Unless you launch a site there, we redirect <code>www</code> to its parent domain for you.</p>\n<h2>No need for a subdirectory per page</h2>\n<p>Name your page <code>hello.html</code> and access it at <code>/hello</code>. Simple and practical.</p>\n<h2>Just enough config</h2>\n<p><code>xmit.toml</code>, which isn't served, offers simple settings. For example:</p>\n<pre><code class=\"language-toml\">fallback = &quot;index.html&quot; # for Single Page Apps\n404 = &quot;404.html&quot; # for custom 404 pages (none in SPAs)\n\n[[headers]] # add a CORS header\nname = &quot;access-control-allow-origin&quot;\nvalue = &quot;*&quot;\n\n[[headers]] # unset &quot;referrer-policy&quot;\nname = &quot;referrer-policy&quot;\n\n[[headers]] # cache assets for a year\nname = &quot;cache-control&quot;\nvalue = &quot;public, max-age=31536000&quot;\non = &quot;^/assets/&quot;\n\n[[redirects]]\nfrom = &quot;^/new/(.*)&quot;\nto = &quot;/$1&quot;\npermanent = true # 301 instead of 307\n</code></pre>\n"
      },
    {
        "id": "https://xmit.dev/posts/brotli/",
        "url": "https://xmit.dev/posts/brotli/",
        "title": "Brotli compression",
        "date_published": "2024-04-18T00:00:00.000Z",
        "content_html": "<p>A quick update to let you know we've enabled Brotli\ncompression, <a href=\"https://indieweb.social/@McNeely/112293430779619468\">as requested by @McNeely on the fediverse</a>.</p>\n<p>This should yield better performance with modern browsers, as Brotli achieves higher ratios than gzip.</p>\n<p>We moved compression from request time with caching, to upload time with full persistence. This should improve tail\nlatencies a fair bit, notably with larger media like photos, audio, and video, at the cost of slower uploads.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/pcarrier-com/",
        "url": "https://xmit.dev/posts/pcarrier-com/",
        "title": "Migrating pcarrier.com: over a GB, and a zoo of domain redirects",
        "date_published": "2024-05-04T00:00:00.000Z",
        "content_html": "<p>There really is joy in using one's own software to solve one's own problems. I can't help but share my experience migrating\nmy personal website to <a href=\"https://xmit.co/\">xmit.co</a>, from a small VPS running nginx. I won't argue the benefits, having already\ndone so in <a href=\"/posts/origin/\">our first post</a> (though I failed to point out what might be the most obvious to me, high availability\nthrough redundancy).</p>\n<p>I have domain acquisition syndrome. For my name alone, over the years I've collected <code>gcarrier.fr</code>, <code>pcarrier.ca</code>,\n<code>pcarrier.fr</code>, <code>pcarrier.com</code>, <code>rrier.ca</code>, <code>rrier.fr</code>.\nThe last two are merely a domain hack so I could be emailed at <code>pc@rrier.ca</code> &amp; <code>pc@rrier.fr</code>.</p>\n<p><a href=\"https://pcarrier.com/\"><code>pcarrier.com</code></a> is the only web property left standing; everything else redirects to it,\nwith a quick hack so I could be found through <code>@pc@rrier.ca</code> &amp; <code>@pc@rrier.fr</code> on the fediverse.</p>\n<p>I already had an organization <code>#19: perso</code>, so I went straight to DNS, where I created the following records for all domains:</p>\n<pre><code>@ CNAME 19.xmit.co.\n* CNAME 19.xmit.co.\n@ TXT &quot;xmit=19&quot;\n</code></pre>\n<p>Then I launched my &gt;1GB web directory with <code>xmit pcarrier.com</code>. That crashed halfway through the upload.\nImplemented chunked uploads in the <code>xmit</code> CLI, released it, and finally relaunched with caching rules in <code>xmit.toml</code>:</p>\n<pre><code>[[headers]]\nname = &quot;Cache-Control&quot;\nvalue = &quot;public, max-age=31536000&quot;\non = &quot;/fonts/.*&quot;\n</code></pre>\n<p>For all the other domains, I launched a separate site with <code>xmit rrier.ca</code> from a directory with a lone <code>xmit.toml</code>:</p>\n<pre><code>[[headers]]\non = &quot;^/.well-known/webfinger$&quot;\nname = &quot;access-control-allow-origin&quot;\nvalue = &quot;*&quot;\n\n[[redirects]]\nfrom = &quot;^/.well-known/webfinger$&quot;\nto = &quot;https://mastodon.social/.well-known/webfinger?resource=acct%3Apcarrier%40mastodon.social&quot;\n\n[[redirects]]\nfrom = &quot;^/(.*)&quot;\nto = &quot;https://pcarrier.com/$1&quot;\npermanent = true\n</code></pre>\n<p>After that, I headed to the <a href=\"https://xmit.co/admin\">xmit admin</a>, clicked on the newly created site,\nrenamed it to <code>pcarrier.com redirs</code>, and added all the other domains on it:</p>\n<p><img src=\"/img/domains.webp\" alt=\"domains\"></p>\n<p>That's all. I thought this worth sharing, though looking back this migration didn't amount to much.\nHappy to be building a rather boring piece of infrastructure.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/small-things/",
        "url": "https://xmit.dev/posts/small-things/",
        "title": "Shipping small things (0pw.me, 1pw.me, found.as)",
        "date_published": "2024-05-17T00:00:00.000Z",
        "content_html": "<p>As the design for <a href=\"https://signali.ng\">signali.ng</a> awaits reviews, I found myself with a fair bit of time and\ninfrastructure readily available. This gave me the unique opportunity of putting small non-commercial services together\nquickly.</p>\n<h2><a href=\"https://0pw.me\">0pw.me: sign<del> up</del> to store</a></h2>\n<p>The idea is simple: no sign up, no account, anybody can maintain a 64KiB chunk of data by associating it to a public\nkey.</p>\n<p>As the updates must be <a href=\"https://en.wikipedia.org/wiki/Digital_signature\">signed</a> using the corresponding private key,\nsomething verified by both the infrastructure and serious clients, only people with the private key can update it,\nand only people with the private key and maintainers of the service can delete it.</p>\n<p>To make it easy and lightweight on web clients without trusting NIST curves, we adopted\n<a href=\"https://github.com/dchest/tweetnacl-js\">TweetNaCl.js</a>'s cryptography.</p>\n<p>To support arbitrary chunks of data without going through base64 or the like, the protocol is based\non <a href=\"https://cbor.io\">CBOR</a> rather than JSON or HTTP paths.</p>\n<p>To avoid ever logging public keys, reads pass the key being looked up in a POST body.\nDon't disclose the public key and only maintainers of the service know where to get your data.</p>\n<p>Encrypt your data and nobody can. Which leads me to the first app using this service,</p>\n<h2><a href=\"https://1pw.me\">1pw.me — password → page</a></h2>\n<p>Again, the idea is simple: no sign up, no account, anybody can associate up to 64KB of text by associating it to a\npassword.</p>\n<p>Of course, it leverages <a href=\"https://0pw.me\">0pw.me</a> to store the encrypted data. <a href=\"https://0pw.me\">0pw.me</a> was built for it,\nreally. And it was built, as the name might suggest, to store my <a href=\"https://1password.com\">1password</a> secret key so I can\nrecover it on the go, even if I lose all my devices.</p>\n<p>The password is used to derive the private key used for <a href=\"https://0pw.me\">0pw.me</a>'s signature, the one needed to deecrypt\nthe data.\nThis derivation uses 100,000 rounds of SHA-256 PBKDF2 with a static salt.</p>\n<p>There really isn't much more to it, adding up\nto <a href=\"https://github.com/xmit-co/1pw.me/blob/main/src/app.tsx\">only 219 lines of Preact</a> within 30KiB compressed of an\nopen source website whose build is reproducible thanks to the <a href=\"https://vitejs.dev/\">Vite</a> ecosystem.</p>\n<h2><a href=\"https://be.found.as\">found.as names anything</a></h2>\n<p>Feeling inspired by the simplicity of <a href=\"https://0pw.me\">0pw.me</a>, quick editing experience of <a href=\"https://1pw.me\">1pw.me</a>,\nspeedy service of <a href=\"https://xmit.co\">xmit.co</a>, I put together a service to associate a redirection, webpage, or (later)\narbitrary file under 1MB to a URL: <a href=\"https://found.as\">found.as</a>.</p>\n<p>Pick a path, pick a password, see what's there if anything, choose what should be there, whether it be a redirect,\nhand-written HTML, markdown, or an arbitrary file, hit a button, observe the result in a new tab, then share at will,\nknowing you can come back and edit it later.</p>\n<p>It reuses the key derivation from <a href=\"https://1pw.me\">1pw.me</a>, but this time the salt depends on the path being edited,\nguaranteeing that the same strong password can be used for multiple paths without correlation in our store.\nUnlike <a href=\"https://1pw.me\">1pw.me</a>, the data is never encrypted as the purpose is to publish.</p>\n<p>Support for markdown with YAML metadata is implemented entirely in browser allowing for live previews, which combined\nwith <a href=\"https://github.com/kriszyp/cbor-x\">cbor-x</a>, <a href=\"https://github.com/dchest/tweetnacl-js\">TweetNaCl.js</a>,\nand <a href=\"https://preactjs.com/\">Preact</a>, puts the admin UI just under 60 KB compressed; of course, the generated resources\nare served without any overhead.</p>\n<h2>Eat what you cook</h2>\n<p>When I wrote about <a href=\"/posts/pcarrier-com\">migrating pcarrier.com to xmit.co</a>, I missed an opportunity to touch on this\ntopic. There's nothing like building for your own needs, and there's nothing like using what you build.</p>\n<p>Publishing <a href=\"https://found.as\">found.as' landing page</a> through <a href=\"https://be.found.as\">its admin UI</a> was a mind-altering\nmoment. I figured I should support uploading files too, so I could ship <a href=\"https://found.as/favicon.ico\">favicon.ico</a>,\nthe site-wide default icon, without a backend rule for it.</p>\n<p>One day, I might need my <a href=\"https://1password.com\">1password</a> secret key urgently, without access to any of my devices.\nI look forward to it.</p>\n<p>And of course, all those services use <a href=\"https://xmit.co\">xmit.co</a>. They get the exclusive privilege of exposing APIs\nthrough it, something I hope to allow through proxying and a serverless runtime at one point or another. I'll be sure\nto (re-)build with those.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/free-subdomains/",
        "url": "https://xmit.dev/posts/free-subdomains/",
        "title": "Free subdomains for completely free websites",
        "date_published": "2024-05-18T00:00:00.000Z",
        "content_html": "<p>We're now offering free subdomains for completely free websites. In the spirit of the <a href=\"https://indieweb.org/\">IndieWeb</a>,\nI'd recommend owning your own domain, but the tradeoff is that resources will only persist as long as you renew it.\nNothing lasts forever, but as long as we're here and not required to remove your content, using a free subdomain\nguarantees that it remains available.</p>\n<p>So jump into <a href=\"https://xmit.co/docs\">our docs</a>, where you'll be directed to skip DNS setup, and deploy a subdomain\nof <a href=\"https://xmit.dev\">xmit.dev</a> if you like our brand, <a href=\"https://madethis.site\">madethis.site</a> for a personal site. If\nyou're using a personal API key (rather than a team's), it'll require an extra argument on first deploy to specify which\nteam the site should belong to. Otherwise, everything works exactly the same.</p>\n<p>And because it's easy to add and remove domains to sites from the admin interface, even if you do have your own domain,\nyou might as well evaluate <a href=\"https://xmit.co\">xmit.co</a> without any DNS changes.</p>\n<p>Please <a href=\"mailto:help@xmit.dev\">reach out</a> if you'd like to provide different domains to our community.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/slower-launches/",
        "url": "https://xmit.dev/posts/slower-launches/",
        "title": "Slower uploads",
        "date_published": "2024-05-23T00:00:00.000Z",
        "content_html": "<p>A quick update to let you know we now acquire certificates synchronously during uploads.</p>\n<p>This lets us report any issues with the certificate acquisition process in real time.\nIt also means that when we tell you an upload is ready, it's truly ready.</p>\n<p>Unfortunately, this adds up to 30 seconds to the <code>xmit</code> execution time.</p>\n<p>As part of this change, we show the preview URL as part of every upload, even those launched right away.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/form2mail/",
        "url": "https://xmit.dev/posts/form2mail/",
        "title": "Form to mail, sneak peek at new projects",
        "date_published": "2024-10-15T00:00:00.000Z",
        "content_html": "<p>We have yet to properly document the feature, but you can now create forms that send emails to the address of your choosing. Some details <a href=\"https://nothing.pcarrier.com/posts/form2mail/\">on Pierre's blog</a>.</p>\n<p>Started prototyping a mailing list turning blog. Still very early days, and might not get anywhere. Again some details <a href=\"https://nothing.pcarrier.com/posts/xit/\">on Pierre's blog</a>.</p>\n<p>Also thinking of building a web-based (Monaco-powered), offline-first editor, so anyone can write HTML and CSS and start a site without any installs. Either directly from the admin UI, or possibly on another domain altogether. This requires the API surface for <code>xmit download</code>, which gives it a new sense of urgency.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/libk/",
        "url": "https://xmit.dev/posts/libk/",
        "title": "Let It Be Known, trivial dynamic DNS",
        "date_published": "2025-07-19T00:00:00.000Z",
        "content_html": "<p><a href=\"https://libk.org\">libk.org</a> is a dynamic DNS service that provides a very simple interface.</p>\n<p>Not much else to say about it that isn't already <a href=\"https://libk.org\">there</a>:\nit's the typical minimal service we are glad to offer.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/bob/",
        "url": "https://xmit.dev/posts/bob/",
        "title": "Oncle Bob, a desktop app for webweaving",
        "date_published": "2025-11-21T00:00:00.000Z",
        "content_html": "<p><a href=\"https://onclebob.com\">onclebob.com</a> just launched.</p>\n<p>Keeping this brief, but it feels like a milestone: Oncle Bob meaningfully moves the needle in the direction xmit was always meant to head — an effort to simplify putting sites on the web.</p>\n<p>The <code>xmit</code> CLI isn't going anywhere. Professional organizations should still rely on CI/CD pipelines for deployment. The CLI is a wonderful tool for people comfortable taming it.</p>\n<p>But for everyone else, Oncle Bob opens the door to a dramatically better experience around static sites. Now I can write guides for starting a blog on a fully self-managed stack without worrying that mandatory command-line usage will scare readers away.</p>\n<p>I don't know that I'll write such guides myself anytime soon. In the meantime, I loved reading <a href=\"https://www.adamdjbrett.com/blog/deploying-eleventy-xmit/\">Adam's guide</a>, which reminded me that DNS is another pain point worth addressing.</p>\n"
      },
    {
        "id": "https://xmit.dev/posts/analytics/",
        "url": "https://xmit.dev/posts/analytics/",
        "title": "Launching analytics on xmit.co",
        "date_published": "2025-11-28T00:00:00.000Z",
        "content_html": "<p><a href=\"https://xmit.co\">xmit.co</a> now sports server-side analytics. We do not use cookies, record the IP address or geolocation in any way. We do not track sessions.</p>\n<p>Entirely backed by strictly filtered access logs injected in a ClickHouse cluster and queriable from a new tab on the site.</p>\n<h2>What's recorded exactly?</h2>\n<p>For each HTTP request, we record:</p>\n<ul>\n<li>Time of the request</li>\n<li>Site identifier</li>\n<li>Domain (one site can have multiple domains)</li>\n<li>Path of the request (e.g. <code>/</code>, <code>/style.css</code>, etc.)</li>\n<li>Type of client (mobile, tablet, desktop, bot; does a poor job at detecting bots)</li>\n<li>User agent header when available (not exposed to customers today, can prove useful to update type)</li>\n<li>Referrer header when available</li>\n<li>HTTP status code</li>\n<li>Content type of the response</li>\n<li>Size of the response in bytes</li>\n</ul>\n<h2>How do I try it?</h2>\n<p>Head to <a href=\"https://xmit.co/analytics\">xmit.co/analytics</a>.</p>\n"
      }
    ]
}
