<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/style.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Cédric's website</title>
    <link>https://www.cedricbonhomme.org/tags/apache/</link>
    <description>It's all about open source, digital freedom, and art.</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    <lastBuildDate>Sun, 01 Nov 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.cedricbonhomme.org/tags/apache/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Keeping My XSLT-Styled Feed Alive After Chrome Drops It</title>
      <link>https://www.cedricbonhomme.org/2026/11/01/keeping-xslt-alive/</link>
      <pubDate>Sun, 01 Nov 2026 00:00:00 +0000</pubDate>
      <guid>https://www.cedricbonhomme.org/2026/11/01/keeping-xslt-alive/</guid>
      <description>&lt;p&gt;Last year I gave my RSS feed &lt;a href=&#34;https://www.cedricbonhomme.org/2025/08/18/rss-xslt-styling/&#34;&gt;a fresh coat of XSLT&lt;/a&gt;,&#xA;so that a curious visitor opening &lt;a href=&#34;https://www.cedricbonhomme.org/blog/index.xml&#34;&gt;https://www.cedricbonhomme.org/blog/index.xml&lt;/a&gt;&#xA;in a browser would see a friendly little landing page instead of angle-bracket&#xA;soup.&lt;/p&gt;&#xA;&lt;p&gt;This evening I opened that feed in Chromium, and was greeted by a big red&#xA;banner:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;This site uses XSLT; that functionality is being removed from this browser&#xA;very soon. When that happens, this page will likely no longer display&#xA;correctly.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Well. My vinyl collection just got a letter from the landlord.&lt;/p&gt;&#xA;&lt;h3 id=&#34;google-is-killing-xslt&#34;&gt;Google Is Killing XSLT&lt;/h3&gt;&#xA;&lt;p&gt;This is not a surprise. Google &lt;a href=&#34;https://developer.chrome.com/docs/web-platform/deprecating-xslt&#34;&gt;announced it in 2025&lt;/a&gt;:&#xA;both the &lt;code&gt;XSLTProcessor&lt;/code&gt; JavaScript API and the &lt;code&gt;&amp;lt;?xml-stylesheet?&amp;gt;&lt;/code&gt;&#xA;processing instruction (when it points to XSLT) are going away. The&#xA;arguments are that browsers still rely on old C libraries such as libxslt,&#xA;which keep producing memory-safety bugs, and that only about 0.02% of page&#xA;loads use XSLT at all. Firefox and WebKit have signalled the same intention.&lt;/p&gt;&#xA;&lt;p&gt;The timeline is short:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Chrome 143&lt;/strong&gt; (December 2025): official deprecation, warnings in the&#xA;console.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Chrome 158&lt;/strong&gt; (November 17, 2026): XSLT stops working on Stable, except for&#xA;sites in an Origin Trial and companies using an Enterprise Policy.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Chrome 176&lt;/strong&gt; (August 2027): the Origin Trial and Enterprise Policy end, and&#xA;XSLT is gone for everyone.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;So in about two weeks, my pretty feed becomes raw XML again for most&#xA;visitors. I understand the security argument. I still like XSLT. A&#xA;stylesheet that turns data into a page, with no JavaScript, no framework and&#xA;no build pipeline, is a lovely idea.&lt;/p&gt;&#xA;&lt;h3 id=&#34;move-the-transform-not-the-technology&#34;&gt;Move the Transform, Not the Technology&lt;/h3&gt;&#xA;&lt;p&gt;The trick, which I had already used for the feeds of&#xA;&lt;a href=&#34;https://www.vulnerability-lookup.org&#34;&gt;Vulnerability-Lookup&lt;/a&gt;, is simple:&#xA;if the browser no longer wants to run the XSLT, run it yourself and send the&#xA;result. Same stylesheet, different place.&lt;/p&gt;&#xA;&lt;p&gt;Vulnerability-Lookup is a Flask application, so it applies the stylesheet&#xA;with lxml on each request. This website is a static Hugo site, so the&#xA;transform happens once, at build time, with good old &lt;code&gt;xsltproc&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-make&#34; data-lang=&#34;make&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;build&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;hugo --cleanDestinationDir&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;MAKE&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt; feeds-html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;feeds-html&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;@grep -rl --include&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;index.xml &lt;span class=&#34;s1&#34;&gt;&amp;#39;xml-stylesheet&amp;#39;&lt;/span&gt; public/ &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; -r f&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;xsltproc --nonet static/style.xsl &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$$&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;f&amp;#34;&lt;/span&gt; &amp;gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$$&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;f.html&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; 1&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every feed that references the stylesheet now has a pre-rendered HTML twin:&#xA;&lt;code&gt;/blog/index.xml&lt;/code&gt; gets &lt;code&gt;/blog/index.xml.html&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;one-url-two-representations&#34;&gt;One URL, Two Representations&lt;/h3&gt;&#xA;&lt;p&gt;The feed URL must not change: it is what people copy into their newsreader.&#xA;So the server decides which version to send, based on the &lt;code&gt;Accept&lt;/code&gt; header.&#xA;When you open a link, browsers send an &lt;code&gt;Accept&lt;/code&gt; header that starts with&#xA;&lt;code&gt;text/html&lt;/code&gt;. Newsreaders ask for &lt;code&gt;application/rss+xml&lt;/code&gt;, &lt;code&gt;application/atom+xml&lt;/code&gt;&#xA;or &lt;code&gt;application/xml&lt;/code&gt; first.&lt;/p&gt;&#xA;&lt;p&gt;The site is served by Apache, so a few lines in &lt;code&gt;.htaccess&lt;/code&gt; do the job:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;IfModule&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;mod_rewrite.c&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;RewriteEngine&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;On&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;RewriteCond&lt;/span&gt; %{HTTP_ACCEPT} ^text/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;RewriteCond&lt;/span&gt; %{REQUEST_FILENAME}.html -f&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;RewriteRule&lt;/span&gt; ^(.+\.xml)$ $1.html [L]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;/IfModule&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;IfModule&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;mod_headers.c&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;lt;FilesMatch&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;\.xml(\.html)?$&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;Header&lt;/span&gt; append Vary Accept&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;lt;/FilesMatch&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;/IfModule&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;Vary: Accept&lt;/code&gt; header tells caches that the same URL has two&#xA;representations, so a newsreader never gets the HTML page from a cache that&#xA;a browser filled.&lt;/p&gt;&#xA;&lt;p&gt;You can check both behaviours with curl:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ curl -sI -H &lt;span class=&#34;s1&#34;&gt;&amp;#39;Accept: text/html&amp;#39;&lt;/span&gt; https://www.cedricbonhomme.org/blog/index.xml &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep -i content-type&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;content-type: text/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ curl -sI https://www.cedricbonhomme.org/blog/index.xml &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep -i content-type&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;content-type: application/xml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;a-few-details&#34;&gt;A Few Details&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The stylesheet must be XSLT 1.0. Mine said &lt;code&gt;version=&amp;quot;3.0&amp;quot;&lt;/code&gt; but only used&#xA;1.0 features, and libxslt, like browsers, only implements 1.0.&lt;/li&gt;&#xA;&lt;li&gt;With &lt;code&gt;method=&amp;quot;html&amp;quot;&lt;/code&gt;, the literal &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element should not carry the&#xA;XHTML namespace, otherwise libxslt serializes it as XML (&lt;code&gt;&amp;lt;meta&amp;gt;&amp;lt;/meta&amp;gt;&lt;/code&gt;).&#xA;Adding &lt;code&gt;doctype-system=&amp;quot;about:legacy-compat&amp;quot;&lt;/code&gt; to &lt;code&gt;xsl:output&lt;/code&gt; gives a proper&#xA;HTML5 doctype, so the page renders in standards mode.&lt;/li&gt;&#xA;&lt;li&gt;Firefox still receives the HTML, like every other browser. If a browser&#xA;ever gets the raw XML, the &lt;code&gt;&amp;lt;?xml-stylesheet?&amp;gt;&lt;/code&gt; instruction is still there&#xA;as a fallback.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Google also offers a WASM &lt;a href=&#34;https://developer.chrome.com/docs/web-platform/deprecating-xslt&#34;&gt;polyfill&lt;/a&gt;&#xA;that you load with a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag inside the XML. It works, but it means&#xA;shipping JavaScript to style a document that needs none. Doing the transform&#xA;on the server is simpler, faster for the visitor, and does not depend on any&#xA;browser keeping anything alive.&lt;/p&gt;&#xA;&lt;h3 id=&#34;so-long-live-xslt&#34;&gt;So, Long Live XSLT?&lt;/h3&gt;&#xA;&lt;p&gt;In a way, yes. XSLT is leaving the browser, but my &lt;code&gt;style.xsl&lt;/code&gt; remains the&#xA;single source of the feed page design. It just runs on my machine now, instead&#xA;of yours.&lt;/p&gt;&#xA;&lt;p&gt;Long live RSS. Long live XSLT, wherever it runs. And long live&#xA;&lt;a href=&#34;https://sr.ht/~cedric/newspipe&#34;&gt;Newspipe&lt;/a&gt;!&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
