<?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: clauxel</title>
    <description>The latest articles on DEV Community by clauxel (@clauxel_fc4a8ded760e880c1).</description>
    <link>https://dev.to/clauxel_fc4a8ded760e880c1</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%2F4053845%2Ffbd59b71-a44d-4336-875e-202f564c53a0.png</url>
      <title>DEV Community: clauxel</title>
      <link>https://dev.to/clauxel_fc4a8ded760e880c1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/clauxel_fc4a8ded760e880c1"/>
    <language>en</language>
    <item>
      <title>How I Consolidated HTTP, WWW, and index.html URLs with a Cloudflare Worker</title>
      <dc:creator>clauxel</dc:creator>
      <pubDate>Mon, 10 Aug 2026 10:24:43 +0000</pubDate>
      <link>https://dev.to/clauxel_fc4a8ded760e880c1/how-i-consolidated-http-www-and-indexhtml-urls-with-a-cloudflare-worker-bm7</link>
      <guid>https://dev.to/clauxel_fc4a8ded760e880c1/how-i-consolidated-http-www-and-indexhtml-urls-with-a-cloudflare-worker-bm7</guid>
      <description>&lt;p&gt;Google Search Console's &lt;strong&gt;"Page with redirect"&lt;/strong&gt; report often looks more alarming than it is.&lt;/p&gt;

&lt;p&gt;On a static site I maintain, Google discovered several historical forms of the same pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://www.example.com/index.html
http://example.com/index.html
https://www.example.com/index.html
https://example.com/index.html
https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the last URL was supposed to be indexed. The others were not separate pages; they were alternate entry points left behind by older links, browser history, and previous deployments.&lt;/p&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every legacy URL should reach one canonical URL in one permanent redirect, and the canonical page should return 200.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post shows the Cloudflare Worker pattern I used, the signals I aligned around it, and the checks that kept the redirect logic from creating new problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Define the URL invariant first
&lt;/h2&gt;

&lt;p&gt;Before writing code, choose one representation for every page.&lt;/p&gt;

&lt;p&gt;For this example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS is mandatory.&lt;/li&gt;
&lt;li&gt;The apex domain is canonical; &lt;code&gt;www&lt;/code&gt; redirects away.&lt;/li&gt;
&lt;li&gt;The homepage is &lt;code&gt;/&lt;/code&gt;, never &lt;code&gt;/index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Directory pages end with a slash.&lt;/li&gt;
&lt;li&gt;Known legacy paths map to their closest equivalent page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing these rules down matters. If the Worker, canonical tags, sitemap, and internal links disagree, crawlers receive conflicting signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Run the Worker before static assets
&lt;/h2&gt;

&lt;p&gt;With Cloudflare Workers Static Assets, the Worker needs to see the request before an asset is served:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"canonical-url-worker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compatibility_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-27"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"directory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ASSETS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"run_worker_first"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudflare documents the &lt;code&gt;ASSETS&lt;/code&gt; binding and &lt;code&gt;run_worker_first&lt;/code&gt; behavior in its &lt;a href="https://developers.cloudflare.com/workers/static-assets/binding/" rel="noopener noreferrer"&gt;Static Assets documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Normalize host, protocol, and legacy paths in one pass
&lt;/h2&gt;

&lt;p&gt;Here is a reduced version of the redirect middleware:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CANONICAL_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;legacyPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&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;/index.html&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;/old-guide&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;/guides/new-guide/&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;/old-guide/&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;/guides/new-guide/&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;/guides/new-guide/index.html&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;/guides/new-guide/&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incoming&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;`www.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CANONICAL_HOST&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CANONICAL_HOST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mappedPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;legacyPaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mappedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mappedPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASSETS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is that protocol, host, and path are normalized before returning a response. A request for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://www.example.com/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;goes directly to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That avoids a redirect chain such as HTTP -&amp;gt; HTTPS -&amp;gt; non-WWW -&amp;gt; root.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;URL&lt;/code&gt; copy also preserves the query string by default. Do not delete arbitrary parameters unless you know they are non-functional. Removing a parameter that changes page content can merge pages that are not actually equivalent.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keep redirects specific
&lt;/h2&gt;

&lt;p&gt;It is tempting to redirect every unknown path to the homepage. I avoid that.&lt;/p&gt;

&lt;p&gt;A removed page should redirect only when there is a genuinely equivalent destination. Otherwise it should return a proper 404 or 410. Sending every missing URL to &lt;code&gt;/&lt;/code&gt; confuses users, hides broken links, and can be treated as a soft 404.&lt;/p&gt;

&lt;p&gt;The explicit &lt;code&gt;Map&lt;/code&gt; makes migrations reviewable. It also prevents an innocent path rewrite from affecting API endpoints or static files.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Align every canonicalization signal
&lt;/h2&gt;

&lt;p&gt;A 301 is strong, but it should not be the only correct signal.&lt;/p&gt;

&lt;p&gt;Each final HTML page includes a self-referencing canonical:&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;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/guides/new-guide/"&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;The XML sitemap lists only final HTTPS, non-WWW URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://example.com/guides/new-guide/&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internal navigation also links directly to the final URL, not to a URL that needs a redirect.&lt;/p&gt;

&lt;p&gt;Google's canonicalization guidance recommends self-referencing canonicals and consistent internal links. Its sitemap documentation likewise says to include the preferred canonical URLs rather than every duplicate form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls" rel="noopener noreferrer"&gt;Canonical URL best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap" rel="noopener noreferrer"&gt;Build and submit a sitemap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Test the entire redirect matrix
&lt;/h2&gt;

&lt;p&gt;I use HEAD requests to verify both the source and destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; http://www.example.com/index.html
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://www.example.com/index.html
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com/index.html
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first three should return something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2 301
location: https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final request should return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also test a real 404, query parameters, nested legacy paths, and any API routes. A redirect system is not finished until its negative cases behave correctly.&lt;/p&gt;

&lt;p&gt;For automated checks, a small table-driven test is enough:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt; &lt;span class="o"&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;http://www.example.com/index.html&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;https://example.com/&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;https://www.example.com/index.html&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;https://example.com/&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;https://example.com/index.html&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;https://example.com/&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assert that every source returns 301, has the exact expected &lt;code&gt;Location&lt;/code&gt;, and reaches a 200 response without another hop.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Interpret Search Console correctly
&lt;/h2&gt;

&lt;p&gt;After deployment, old URLs may remain in Search Console under &lt;strong&gt;Page with redirect&lt;/strong&gt; for a while. That category is normally expected: Google discovered the source URL, followed the redirect, and chose not to index the source.&lt;/p&gt;

&lt;p&gt;Google explicitly describes permanent server-side redirects as a signal that the target should be canonical in its &lt;a href="https://developers.google.com/search/docs/crawling-indexing/301-redirects" rel="noopener noreferrer"&gt;redirect documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The useful inspection target is the final URL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it return 200?&lt;/li&gt;
&lt;li&gt;Is it indexable?&lt;/li&gt;
&lt;li&gt;Does its canonical point to itself?&lt;/li&gt;
&lt;li&gt;Is it present in the sitemap?&lt;/li&gt;
&lt;li&gt;Do internal links use it directly?&lt;/li&gt;
&lt;li&gt;Does URL Inspection show the intended canonical?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running "Validate fix" on correctly redirected source URLs does not make those sources indexable, nor should it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Using 302 for a permanent move.&lt;/strong&gt; Temporary redirects do not communicate the same long-term intent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creating redirect chains.&lt;/strong&gt; Normalize all dimensions in one response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaving old URLs in the sitemap.&lt;/strong&gt; The sitemap should describe destinations, not historical routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using a canonical tag instead of a redirect.&lt;/strong&gt; If a duplicate URL should never be visited, redirect it at the server or edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirecting unrelated 404s to the homepage.&lt;/strong&gt; Map only equivalent content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting the final 200 check.&lt;/strong&gt; A perfect 301 that lands on an error page is still broken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixing slash conventions.&lt;/strong&gt; Pick one format and make routes, canonicals, and links agree.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A small real-world implementation
&lt;/h2&gt;

&lt;p&gt;I applied this pattern to &lt;a href="https://lavenderessentialoilbenefits.online/" rel="noopener noreferrer"&gt;the live informational site I maintain&lt;/a&gt;. Its old HTTP, WWW, and &lt;code&gt;index.html&lt;/code&gt; forms converge on the HTTPS apex homepage, while the final page returns 200. The linked site is mine and contains product links; I include it here only as a disclosed live implementation of the redirect pattern.&lt;/p&gt;

&lt;p&gt;The main lesson was not "remove every URL from the Search Console report." It was to make every signal agree on one destination and then evaluate the destination itself.&lt;/p&gt;

&lt;p&gt;Once that invariant is enforced at the edge, duplicate URL cleanup becomes predictable instead of mysterious.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>K3Nova: A practical Kimi K3 workspace for developers</title>
      <dc:creator>clauxel</dc:creator>
      <pubDate>Wed, 29 Jul 2026 19:49:49 +0000</pubDate>
      <link>https://dev.to/clauxel_fc4a8ded760e880c1/k3nova-a-practical-kimi-k3-workspace-for-developers-2ibm</link>
      <guid>https://dev.to/clauxel_fc4a8ded760e880c1/k3nova-a-practical-kimi-k3-workspace-for-developers-2ibm</guid>
      <description>&lt;p&gt;K3Nova is a practical workspace and guide hub for developers evaluating Kimi K3 in real projects.&lt;/p&gt;

&lt;p&gt;Kimi K3 is getting attention because the model discussion is not only about chat. Developers are looking at long-context coding, agent workflows, benchmark momentum, local deployment tradeoffs, and how to make Kimi Code-style work easier to evaluate.&lt;/p&gt;

&lt;p&gt;That is where K3Nova is useful. It keeps the Kimi K3 experience organized around concrete decisions: where to start, which official links matter, how pricing and upload limits affect planning, what hardware or self-hosting requirements look like, and how local agents and coding workflows fit together.&lt;/p&gt;

&lt;p&gt;The site is intentionally practical: one homepage, a focused guide hub, and deep pages for setup, benchmarks, plugins, uninstall paths, model size, MoE notes, local AI, and agent workflows.&lt;/p&gt;

&lt;p&gt;Useful K3Nova starting points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=home" rel="noopener noreferrer"&gt;home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-k3-guides/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=guide-hub" rel="noopener noreferrer"&gt;guide-hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-code-plugins/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=plugins" rel="noopener noreferrer"&gt;plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-code-uninstall-guide/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=code-uninstall-guide" rel="noopener noreferrer"&gt;code-uninstall-guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/remove-kimi-code-cli/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=remove-cli" rel="noopener noreferrer"&gt;remove-cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-k3-benchmark-rankings/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=benchmarks" rel="noopener noreferrer"&gt;benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/export-kimi-website-locally/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=local-export" rel="noopener noreferrer"&gt;local-export&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-k3-hardware-checklist/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=hardware-checklist" rel="noopener noreferrer"&gt;hardware-checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/uninstall-kimi-code/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=uninstall-kimi-code" rel="noopener noreferrer"&gt;uninstall-kimi-code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-models-guide/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=models-guide" rel="noopener noreferrer"&gt;models-guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-upload-limits/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=upload-limits" rel="noopener noreferrer"&gt;upload-limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/rtx-6000-ada-kimi-local-ai/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=rtx-6000-ada" rel="noopener noreferrer"&gt;rtx-6000-ada&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-official-site/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=official-site" rel="noopener noreferrer"&gt;official-site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-ai-official-links/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=official-links" rel="noopener noreferrer"&gt;official-links&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-pricing-guide/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=pricing-guide" rel="noopener noreferrer"&gt;pricing-guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-web-address/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=web-address" rel="noopener noreferrer"&gt;web-address&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/how-big-is-kimi-k3/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=model-size" rel="noopener noreferrer"&gt;model-size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/can-kimi-k3-run-locally/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=local-run" rel="noopener noreferrer"&gt;local-run&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kilo-code-api-key-kimi-code/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=kilo-api-key" rel="noopener noreferrer"&gt;kilo-api-key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-k3-local-agent-guide/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=local-agent" rel="noopener noreferrer"&gt;local-agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/self-host-kimi-k3-minimum-requirements/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=self-hosting" rel="noopener noreferrer"&gt;self-hosting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-moe-model-explained/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=moe-explained" rel="noopener noreferrer"&gt;moe-explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-vs-deepseek-openclaw/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=deepseek-compare" rel="noopener noreferrer"&gt;deepseek-compare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-k3-code-size/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=code-size" rel="noopener noreferrer"&gt;code-size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3nova.com/kimi-k3-agent-locally/?utm_source=dev-to&amp;amp;utm_medium=article&amp;amp;utm_campaign=k3nova_current_pages_20260730&amp;amp;utm_content=agent-locally" rel="noopener noreferrer"&gt;agent-locally&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams comparing Kimi K3 with other developer AI systems, K3Nova is a positive and implementation-focused place to begin: it turns model curiosity into a checklist, guide set, and navigation layer that builders can actually use.&lt;/p&gt;

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