<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Mehdi’s articles</title>
        <link>https://blog.mehdi.cc</link>
        <description>A chronological gathering of… articles.</description>
        <lastBuildDate>Fri, 20 Feb 2026 20:05:57 GMT</lastBuildDate>
        <docs>https://www.rssboard.org/rss-specification</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <ttl>2880</ttl>
        <copyright>Copyright © 2023-present, Mehdi Merah</copyright>
        <atom:link href="https://blog.mehdi.cc/feed-articles-only.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Seeding a MySQL 8 table with geographic coordinates using Laravel]]></title>
            <link>https://blog.mehdi.cc/articles/laravel-mysql-geography-seed</link>
            <guid isPermaLink="false">https://blog.mehdi.cc/articles/laravel-mysql-geography-seed</guid>
            <pubDate>Fri, 10 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Who would expect there’s a SQL column type for this?]]></description>
            <content:encoded><![CDATA[<p>Today I learned that MySQL 8 has column types to store geographic coordinates, the simplest of them probably being <a href="https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html" target="_blank" rel="noreferrer"><code>POINT</code></a>. It’s less straightforward than what I expected, so here’s how I managed to seed my database using <a href="https://laravel.com" target="_blank" rel="noreferrer">Laravel</a>.</p>
<hr>
<h1 id="seeding-a-mysql-8-table-with-geographic-coordinates-using-laravel-11" tabindex="-1">Seeding a MySQL 8 table with geographic coordinates using Laravel 11 <a class="header-anchor" href="#seeding-a-mysql-8-table-with-geographic-coordinates-using-laravel-11" aria-label="Permalink to &quot;Seeding a MySQL 8 table with geographic coordinates using Laravel 11&quot;">&ZeroWidthSpace;</a></h1>
<datetime :date="$frontmatter.publishedAt" formatter="longdate"/><tags/><p>Stack for this post:</p>
<ul>
<li>Laravel 11.5.0</li>
<li>PHP 8.3.6</li>
<li>MySQL 8.1</li>
</ul>
<details class="details custom-block"><summary>Solution without explanation</summary>
<div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-4ulHU" id="tab-PIFWE1Z" checked><label data-title="2024_05_09_193405_create_places_table.php" for="tab-PIFWE1Z">2024_05_09_193405_create_places_table.php</label><input type="radio" name="group-4ulHU" id="tab-qhm6VyS" ><label data-title="PlacesSeeder.php" for="tab-qhm6VyS">PlacesSeeder.php</label></div><div class="blocks">
<div class="language-php vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;?</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">use</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Database\Seeders\PlacesSeeder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">use</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Illuminate\Database\Migrations\Migration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">use</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Illuminate\Database\Schema\Blueprint</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">use</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Illuminate\Support\Facades\Schema</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> extends</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Migration</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    /**</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     * Run the migrations.</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> up</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        Schema</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">create</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'places'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Blueprint</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $table) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            $table</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            $table</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'name'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            $table</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">geography</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'coordinates'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'point'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        });</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> PlacesSeeder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    /**</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     * Reverse the migrations.</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> down</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        Schema</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dropIfExists</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'places'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span></code></pre>
</div><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;?</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">namespace</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Database\Seeders</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">use</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Illuminate\Database\Seeder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">use</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Illuminate\Support\Facades\DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> PlacesSeeder</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> extends</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Seeder</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    /**</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     * Run the database seeds.</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">     */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        $montgomery </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">            'name'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'MONTGOMERY'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line highlighted"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">            'coordinates'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> DB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">raw</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'ST_SRID(Point(50.838006, 4.40897), 4326)'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        ];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        DB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">table</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'places'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">insert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([$mongtomery]);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div></div></div>
</details>
<h2 id="coordinates-in-the-database-why" tabindex="-1">Coordinates in the database, why? <a class="header-anchor" href="#coordinates-in-the-database-why" aria-label="Permalink to &quot;Coordinates in the database, why?&quot;">&ZeroWidthSpace;</a></h2>
<p>Among <a href="https://dev.mysql.com/doc/refman/8.3/en/spatial-function-reference.html" target="_blank" rel="noreferrer">all MySQL spatial functions</a>, <a href="https://dev.mysql.com/blog-archive/geography-in-mysql-8-0/" target="_blank" rel="noreferrer">a bunch of them support computations</a>, which might be handy in some scenarios. I absolutely don’t need this for now and I could as well use a <a href="https://dev.mysql.com/doc/refman/8.3/en/json.html" target="_blank" rel="noreferrer"><code>JSON</code> type</a> or a <a href="https://dev.mysql.com/doc/refman/8.3/en/char.html" target="_blank" rel="noreferrer"><code>VARCHAR</code></a> to store the latitude and longitude of a place, but I gave it a try since I like to express data in a meaningful way, and for science’s sake!</p>
<h2 id="create-a-field-for-coordinates-in-a-laravel-migration" tabindex="-1">Create a field for coordinates in a Laravel migration <a class="header-anchor" href="#create-a-field-for-coordinates-in-a-laravel-migration" aria-label="Permalink to &quot;Create a field for coordinates in a Laravel migration&quot;">&ZeroWidthSpace;</a></h2>
<p>In Laravel, you can use the <a href="https://laravel.com/docs/11.x/migrations#column-method-geography" target="_blank" rel="noreferrer"><code>geography</code> column type</a> to declare a <a href="https://dev.mysql.com/doc/refman/8.3/en/spatial-type-overview.html" target="_blank" rel="noreferrer"><code>POINT</code></a> in your table <a href="https://laravel.com/docs/11.x/migrations#introduction" target="_blank" rel="noreferrer">migration</a>:</p>
<div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Schema</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">create</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'places'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Blueprint</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $table) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    $table</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    $table</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'name'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    $table</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">geography</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'coordinates'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'point'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
</div><p>The actual SQL query ran by this migration:</p>
<div class="language-sql vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">CREATE</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> TABLE</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">places</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">` (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  `id`</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> bigint</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> unsigned </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">NOT NULL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  `name`</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> varchar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">255</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">NOT NULL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line highlighted"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  `coordinates`</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> point</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> /*!80003 SRID 4326 */</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> NOT NULL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  PRIMARY KEY</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`id`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
</div><p>The comment in the <code>coordinates</code> field shows it uses a <a href="https://en.wikipedia.org/wiki/Spatial_reference_system" target="_blank" rel="noreferrer"><code>SRID</code></a> of <a href="https://developers.arcgis.com/documentation/spatial-references/#4326---gps" target="_blank" rel="noreferrer"><code>4326</code></a>, which is one of the most common ways to project spatial data on a 2D map. It seems that’s what SRID are all about.</p>
<h2 id="insert-geographic-coordinates-in-a-table" tabindex="-1">Insert geographic coordinates in a table <a class="header-anchor" href="#insert-geographic-coordinates-in-a-table" aria-label="Permalink to &quot;Insert geographic coordinates in a table&quot;">&ZeroWidthSpace;</a></h2>
<p>Let’s see how to store an entry containing geographic coordinates, first using plain SQL, then using Laravel.</p>
<h3 id="insert-geographic-coordinates-using-plain-sql" tabindex="-1">Insert geographic coordinates using plain SQL <a class="header-anchor" href="#insert-geographic-coordinates-using-plain-sql" aria-label="Permalink to &quot;Insert geographic coordinates using plain SQL&quot;">&ZeroWidthSpace;</a></h3>
<p>You might think it’s straightforward to insert an entry in this table using the <a href="https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html#function_point" target="_blank" rel="noreferrer"><code>Point</code></a> MySQL function:</p>
<div class="language-sql vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">INSERT INTO</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> `places`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`name`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`coordinates`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">VALUES</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'MONTGOMERY'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Point</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">50</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">838006</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">40897</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))</span></span></code></pre>
</div><p>But this query actually fails and throws:</p>
<blockquote>
<p>The SRID of the geometry does not match the SRID of the column 'coordinates'. The SRID of the geometry is 0, but the SRID of the column is 4326. Consider changing the SRID of the geometry or the SRID property of the column.</p>
</blockquote>
<p>We need to attach a SRID to that point, using the <a href="https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html#function_st-srid" target="_blank" rel="noreferrer"><code>ST_SRID</code> function</a>:</p>
<div class="language-sql vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">INSERT INTO</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> `places`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`name`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`coordinates`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">VALUES</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'MONTGOMERY'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Point</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">50</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">838006</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">40897</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) # [!code --]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'MONTGOMERY'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, ST_SRID(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Point</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">50</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">838006</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">40897</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4326</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) # [!code ++]</span></span></code></pre>
</div><p>Now it works, and our table entry contains <code>POINT(50.838006 4.40897),4326</code> in <code>coordinates</code>.</p>
<h3 id="insert-geographic-coordinates-using-laravel-db" tabindex="-1">Insert geographic coordinates using Laravel <code>DB</code> <a class="header-anchor" href="#insert-geographic-coordinates-using-laravel-db" aria-label="Permalink to &quot;Insert geographic coordinates using Laravel `DB`&quot;">&ZeroWidthSpace;</a></h3>
<div class="info custom-block"><p class="custom-block-title">INFO</p>
<p>For now I have not tried with <a href="https://laravel.com/docs/11.x/eloquent#inserting-and-updating-models" target="_blank" rel="noreferrer">Eloquent</a> yet. Feel free to reach out if you have! I’ll update the article with your findings.</p>
</div>
<p>In Laravel, I initially thought this would work:</p>
<div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$montgomery </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'name'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'MONTGOMERY'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line highlighted"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'coordinates'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ST_SRID(Point(50.838006, 4.40897), 4326)'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">DB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">table</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'places'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">insert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([$mongtomery]);</span></span></code></pre>
</div><p>But unlike the previous plain SQL request, it fails:</p>
<blockquote>
<p>SQLSTATE[22003]: Numeric value out of range: 1416 Cannot get geometry object from data you send to the GEOMETRY field</p>
</blockquote>
<p>This is a bit unexpected, so let’s debug! We can inspect the generated SQL query with the undocumented <a href="https://laravel.com/api/11.x/Illuminate/Database/Connection.html#method_pretend" target="_blank" rel="noreferrer"><code>DB::pretend</code> method</a>:</p>
<div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">dd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">DB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pretend</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">fn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() => </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">DB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">table</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'places'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">insert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([$mongtomery])));</span></span></code></pre>
</div><p>We see the <code>ST_SRID</code> function is now unfortunately wrapped by quotes, it’s why it fails.</p>
<div class="language-sql vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">INSERT INTO</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> `places`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`coordinates`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`name`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">VALUES</span></span>
<span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    (</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'ST_SRID(Point(50.838006, 4.40897), 4326)'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'MONTGOMERY'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
</div><p>To work around this, we need to prepare the SQL statement using <a href="https://laravel.com/docs/11.x/queries#raw-expressions" target="_blank" rel="noreferrer"><code>DB::raw</code></a> :</p>
<div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes github-light github-dark has-diff vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$montgomery </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'name'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'MONTGOMERY'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line diff remove"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'coordinates'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ST_SRID(Point(50.838006, 4.40897), 4326)'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span></span>
<span class="line diff add"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'coordinates'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> DB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">raw</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'ST_SRID(Point(50.838006, 4.40897), 4326)'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">DB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">table</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'places'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">insert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([$mongtomery]);</span></span></code></pre>
</div><p>With this change, the generated SQL query is now correct. 👍</p>
<p>It’s somehow weird that Laravel provides a convenient way to create a spatial database column but absolute nothing when it comes to actually dealing with related read-write operations. On the other hand, looking at the spatial specifics (number of possibilities, functions and database engines), it’s probably wiser to keep this out the scope of the framework.</p>
<div class="danger custom-block"><p class="custom-block-title">MySQL is not the only database system.</p>
<p>In Laravel, there are <a href="https://laravel.com/docs/11.x/database#introduction" target="_blank" rel="noreferrer">multiple drivers for various database systems</a>. MySQL is only one of them, and others might not support the same spatial features and functions, so make sure you properly document the supported database of your project. Alternatively, there are probably interesting bits of code in <a href="https://github.com/Angel-Source-Labs/laravel-spatial" target="_blank" rel="noreferrer">angel-source-labs/laravel-spatial</a> if you want to implement bridges/helpers between database systems. If you know a well-maintained package for this, let me know, I’ll put it here.</p>
</div>
]]></content:encoded>
            <author>hi@mehdi.cc (Mehdi Merah)</author>
        </item>
        <item>
            <title><![CDATA[GitHub Markdown syntax for alerts considered harmful]]></title>
            <link>https://blog.mehdi.cc/articles/github-alerts-markdown-syntax</link>
            <guid isPermaLink="false">https://blog.mehdi.cc/articles/github-alerts-markdown-syntax</guid>
            <pubDate>Sat, 03 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[I’ve decided to not use it, it’s confusing and poorly designed.]]></description>
            <content:encoded><![CDATA[<div class="info custom-block"><p class="custom-block-title">Article updated on <datetime date="2024-02-04" formatter="longdate"/></p>
<p><a href="./../articles/github-alerts-markdown-syntax.html#updates-to-this-article">Read all about the changes</a>.</p>
</div>
<p>I had multiple options for the title of this note, from being neutral to opinionated, or even pedantic. I deliberately kept <a href="https://meyerweb.com/eric/comment/chech.html" target="_blank" rel="noreferrer">the worst</a>.</p>
<p>The rant has been written before having discovered <a href="https://github.com/orgs/community/discussions/16925#discussioncomment-2791869" target="_blank" rel="noreferrer">this comment and its answers</a> on the related GitHub community discussion. If you’ve already read what’s there, you won’t learn anything new here. (I am actually glad my observations were all backed by others before I even published them.)</p>
<hr>
<h1 id="github-markdown-syntax-for-alerts-considered-harmful" tabindex="-1">GitHub Markdown syntax for alerts considered harmful <a class="header-anchor" href="#github-markdown-syntax-for-alerts-considered-harmful" aria-label="Permalink to &quot;GitHub Markdown syntax for alerts considered harmful&quot;">&ZeroWidthSpace;</a></h1>
<datetime :date="$frontmatter.publishedAt" formatter="longdate"/><p>Vitepress <a href="https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md#100-rc40-2024-1-22" target="_blank" rel="noreferrer">1.0.0-rc.40</a> adds <a href="https://vitepress.dev/guide/markdown#github-flavored-alerts" target="_blank" rel="noreferrer">support for the GitHub syntax for alerts</a> and converts it into <a href="https://vitepress.dev/guide/markdown#custom-containers" target="_blank" rel="noreferrer">custom containers</a>.</p>
<h2 id="the-issues" tabindex="-1">The issues <a class="header-anchor" href="#the-issues" aria-label="Permalink to &quot;The issues&quot;">&ZeroWidthSpace;</a></h2>
<p>Thou I’ve been an early adopter of this syntax on GitHub, I’ve <a href="https://github.com/meduzen/blog/blob/main/CONTRIBUTING.md#markdown-flavor" target="_blank" rel="noreferrer">decided not use it</a> in other contexts:</p>
<div class="language-md vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">md</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">> [</span><span style="--shiki-light:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#DBEDFF;--shiki-dark-text-decoration:underline">!NOTE</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">]  </span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">> Lorem mynote ipsum.</span></span></code></pre>
</div><p>Here’s why:</p>
<ul>
<li>The content of the <s>heading</s> first sentence is predefined: goodbye freedom of writing, or translations.</li>
<li>You have to use the syntax for a <a href="https://daringfireball.net/projects/markdown/syntax#blockquote" target="_blank" rel="noreferrer">blockquote</a>, but it gets replaced by a <code>&lt;div&gt;</code> containing the inner content. This impacts the portability of Markdown documents to other systems: they will create a blockquote, no matter your initial respect for semantic.</li>
<li>You have to remember limitations that <a href="https://github.com/vuejs/vitepress/issues/3512" target="_blank" rel="noreferrer">raise questions</a>. <a href="https://github.com/sinsukehlab/NOTE-test/issues/1" target="_blank" rel="noreferrer">Lot of them</a>.</li>
</ul>
<p>In any system, the previous example would be a paragraph inside a <code>&lt;blockquote&gt;</code>:</p>
<div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">blockquote</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>NOTE Lorem mynote ipsum&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">blockquote</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><p>But this syntax turns them into two paragraphs inside a <code>&lt;div&gt;</code>.</p>
<p>In GitHub:</p>
<div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"markdown-alert markdown-alert-note"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"markdown-alert-title"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">svg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>…&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">svg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> Note&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Lorem mynote ipsum&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><p>In Vitepress:</p>
<div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"note custom-block github-alert"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"custom-block-title"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>NOTE&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Lorem mynote ipsum&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><h2 id="use-vitepress-custom-containers-instead" tabindex="-1">Use Vitepress custom containers instead <a class="header-anchor" href="#use-vitepress-custom-containers-instead" aria-label="Permalink to &quot;Use Vitepress custom containers instead&quot;">&ZeroWidthSpace;</a></h2>
<p>Vitepress <a href="https://vitepress.dev/guide/markdown#custom-containers" target="_blank" rel="noreferrer">custom containers</a> are more suitable in any situation: you get the full power of the previous example, but with full semantic support and the ability to put whatever you want as first sentence:</p>
<div class="language-md vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">md</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::: info My first line</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">This is a paragraph.</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:::</span></span></code></pre>
</div><p>HTML output:</p>
<div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::: </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">&#x3C;!-- 👈 not shown in Vitepress --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"info custom-block"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"custom-block-title"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>My first line&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>This is a paragraph.&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::: </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">&#x3C;!-- 👈 not shown in Vitepress --></span></span></code></pre>
</div><p>And if you are reading it on my blog, it looks like this:</p>
<div class="info custom-block"><p class="custom-block-title">My first sentence</p>
<p>This is a paragraph</p>
</div>
<p>The <code>:::</code> will look disgracious on other systems, but I prefer this trade-off over incorrect semantic: <a href="https://github.com/search?q=repo%3Ameduzen%2Fblog+lang%3AMarkdown+%3A%3A%3A&amp;type=code" target="_blank" rel="noreferrer">looking at my blog</a>, only 5 of 11 occurences (at the time of writing) would have been achievable with the GitHub syntax… and this is assuming I don’t need to customise the first sentence, which I always do, giving a 0 of 11 occurences.</p>
<h2 id="wrapping-up" tabindex="-1">Wrapping up <a class="header-anchor" href="#wrapping-up" aria-label="Permalink to &quot;Wrapping up&quot;">&ZeroWidthSpace;</a></h2>
<p>Instead of having to figure out if I can use the GitHub syntax at a given place, it’s better maintenance to not use it at all. The only use case I can think of for the GitHub syntax outside of GitHub is when you don’t care about semantic (e.g. paper books).</p>
<p>When the GitHub syntax was initially designed, I doubt the flexibility, the semantic or the compatibility with other systems were considered. Actually, <a href="#the-issues">the exact issues I’ve observed</a> were raised immediatly after the initial post went online: exactly <a href="https://github.com/orgs/community/discussions/16925#discussioncomment-2787141" target="_blank" rel="noreferrer">9 hours</a> for the first and <a href="https://github.com/orgs/community/discussions/16925#discussioncomment-2791861" target="_blank" rel="noreferrer">1 day and 58 min.</a> for the second.</p>
<p>I think <a href="https://github.com/vuejs/vitepress/discussions/3540" target="_blank" rel="noreferrer">Vitepress shouldn’t encourage it</a>, and could revert it (<strong>progress</strong>: it’s not reverted but you’ll soon be able to disable it if you want, <a href="#updates-to-this-article">see the updates</a>). I also think GitHub should change its decision and adopt a custom syntax that doesn’t break semantic: a candidate for this is the one used by Vitepress for custom containers, which is also used by <a href="https://docusaurus.io/docs/markdown-features/admonitions" target="_blank" rel="noreferrer">Docusaurus</a>, and for which <a href="https://talk.commonmark.org/t/generic-directives-plugins-syntax/444" target="_blank" rel="noreferrer">extensive discussions</a> exist since a decade ago.</p>
<blockquote>
<p>Hey, but if GitHub reverts it, that would looks bad on my documentation.</p>
</blockquote>
<p>Well, yes, and mine too: and actually it already changed <a href="https://github.com/meduzen/datetime-attribute/commit/26e1234b46c0db7585883ed52d6b371066e37159" target="_blank" rel="noreferrer">at least once</a> since the early implementation. And worst: at work I have documentation that now needs to be ingested in another system that doesn’t give a tilde about this syntax. So I’m very fine with breaking it again, but hopefully for a better design.</p>
<h2 id="updates-to-this-article" tabindex="-1">Updates to this article <a class="header-anchor" href="#updates-to-this-article" aria-label="Permalink to &quot;Updates to this article&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="" tabindex="-1"><datetime date="2024-02-04" formatter="longdate"/> <a class="header-anchor" href="#" aria-label="Permalink to &quot;&lt;datetime date=&quot;2024-02-04&quot; formatter=&quot;longdate&quot;/&gt;&quot;">&ZeroWidthSpace;</a></h3>
<p>Yesterday I shared this article on the GitHub discussions of both <a href="https://github.com/orgs/community/discussions/16925#discussioncomment-8352502" target="_blank" rel="noreferrer">GitHub</a> and <a href="https://github.com/vuejs/vitepress/discussions/3540" target="_blank" rel="noreferrer">Vitepress</a>:</p>
<ul>
<li>Following <a href="https://github.com/vuejs/vitepress/discussions/3540#discussioncomment-8352895" target="_blank" rel="noreferrer">Divyansh Singh’s comment</a>, it is indeed possible to have more than 1 HTML element for the content of a GitHub alert on GitHub, but with unclear limitations: there’s a number of <a href="https://github.com/sinsukehlab/NOTE-test/issues/1" target="_blank" rel="noreferrer">rendering situations tested by Shinsuke Higashiyama</a>.</li>
<li>There‘s <a href="https://github.com/vuejs/vitepress/compare/v1.0.0-rc.41...67a9964c4e6ffdb0e644624d075b8dd20f477686" target="_blank" rel="noreferrer">new ongoing development</a> on Vitepress (to be released in <code>1.0.0-rc.42</code>, I guess) to fix the <a href="https://github.com/vuejs/vitepress/issues/3512" target="_blank" rel="noreferrer">impossibility to have more than 1 block element</a>, and to add a new setting that disables the GitHub syntax alert plugin by setting <a href="https://github.com/vuejs/vitepress/blob/67a9964c4e6ffdb0e644624d075b8dd20f477686/src/node/markdown/markdown.ts#L181-L185" target="_blank" rel="noreferrer"><code>config.markdown.gfmAlerts</code></a> to <code>false</code> in <a href="https://vitepress.dev/reference/site-config#markdown" target="_blank" rel="noreferrer">Vitepress config</a>.</li>
</ul>
<p>I’ve updated the article to reflect these new informations. See <a href="https://github.com/meduzen/blog/pull/45/files" target="_blank" rel="noreferrer">code difference in the repository</a>.</p>
]]></content:encoded>
            <author>hi@mehdi.cc (Mehdi Merah)</author>
        </item>
        <item>
            <title><![CDATA[Downloading Xcode Simulators on macOS Sonoma when the bandwidth is slow]]></title>
            <link>https://blog.mehdi.cc/articles/macos-sonoma-download-xcode-simulators-slow-bandwidth</link>
            <guid isPermaLink="false">https://blog.mehdi.cc/articles/macos-sonoma-download-xcode-simulators-slow-bandwidth</guid>
            <pubDate>Sat, 18 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[It shouldn’t be a pain in the ass, but it is.]]></description>
            <content:encoded><![CDATA[<div class="info custom-block"><p class="custom-block-title">Article updated on <datetime date="2025-02-07" formatter="longdate"/></p>
<p><a href="./../articles/macos-sonoma-download-xcode-simulators-slow-bandwidth.html#updates-to-this-article">Read all about the changes</a>.</p>
</div>
<p>Since <a href="https://www.apple.com/macos/sonoma/" target="_blank" rel="noreferrer">macOS Sonoma</a> (macOS 14), <a href="https://developer.apple.com/xcode/" target="_blank" rel="noreferrer">Xcode</a> doesn’t include any iOS Simulator anymore, so it’s now 50% lighter to download (3.2 GB instead of 7 GB), which is nice… except that downloading the Simulator separately sucks <del datetime="2025-06-07">if your bandwidth is slow</del> <ins datetime="2025-06-07">and will peak below 5 MB/s no matter your bandwidth</ins>.</p>
<hr>
<h1 id="downloading-xcode-simulators-on-macos-sonoma-when-the-bandwidth-is-slow" tabindex="-1">Downloading Xcode Simulators on macOS Sonoma when the bandwidth is slow <a class="header-anchor" href="#downloading-xcode-simulators-on-macos-sonoma-when-the-bandwidth-is-slow" aria-label="Permalink to &quot;Downloading Xcode Simulators on macOS Sonoma when the bandwidth is slow&quot;">&ZeroWidthSpace;</a></h1>
<datetime :date="$frontmatter.publishedAt" formatter="longdate"/><p>There’s one question you might have before we jump in:</p>
<blockquote>
<p>I’m a web developer, should I care about the iOS Simulator?</p>
</blockquote>
<p>Yes! If you are doing web development and wonder if you should install the iOS Simulator, jump to the <a href="#what-are-xcode-simulators-by-the-way">last section</a>.</p>
<h2 id="how-to-download-the-ios-simulator" tabindex="-1">How to download the iOS Simulator <a class="header-anchor" href="#how-to-download-the-ios-simulator" aria-label="Permalink to &quot;How to download the iOS Simulator&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="fail-the-traditional-way" tabindex="-1">Fail: the traditional way <a class="header-anchor" href="#fail-the-traditional-way" aria-label="Permalink to &quot;Fail: the traditional way&quot;">&ZeroWidthSpace;</a></h3>
<p>On macOS, you would normally open Xcode and hit the “get” button from <em>Settings</em> / <em>Platforms</em>.</p>
<picture>
    <source media="(prefers-color-scheme: dark)" srcset="/content/xcode-settings-platforms-dark.webp" type="image/webp"/>
    <source media="(prefers-color-scheme: dark)" srcset="/content/xcode-settings-platforms-dark.png" type="image/png"/>
    <source media="(prefers-color-scheme: light)" srcset="/content/xcode-settings-platforms-light.webp" type="image/webp"/>
    <img src="/content/xcode-settings-platforms-light.png" alt="Xcode platforms settings, showing available platforms (macOS, iOS, watchOS, tvOS)." style="margin-inline: auto;" />
</picture>
<p>My connexion is slow and peak at 21 Mbps (2.6 MB/s.), and downloading from that menu is more often in the 0.1 to 1 MB/s range, and even worse: it will fail after around 2 hours <strong>with no possibility to resume the failing download</strong>!</p>
<h3 id="fail-same-player-try-again-with-the-command-line" tabindex="-1">Fail: same player try again with the command line <a class="header-anchor" href="#fail-same-player-try-again-with-the-command-line" aria-label="Permalink to &quot;Fail: same player try again with the command line&quot;">&ZeroWidthSpace;</a></h3>
<p>Alternatively, the download can be triggered from the command line:</p>
<div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xcodebuild</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -downloadPlatform</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iOS</span></span></code></pre>
</div><p>But unfortunately, it’s as slow as a download from the Xcode app, and you’ll get kicked out the same way after a while without the ability to resume the download. At least the experience is consistent…</p>
<h3 id="success-use-safari" tabindex="-1">Success: use Safari <a class="header-anchor" href="#success-use-safari" aria-label="Permalink to &quot;Success: use Safari&quot;">&ZeroWidthSpace;</a></h3>
<p>Fortunately, along with the <a href="https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes#Install-and-manage-Simulator-runtimes-from-the-command-line" target="_blank" rel="noreferrer">previous method documentation</a> comes a way simpler way to proceed: go to the <a href="https://developer.apple.com/download/all/?q=ios%20Simulator%20runtime" target="_blank" rel="noreferrer">Apple developer downloads website</a> (you have to login with an Apple ID) and download the Simulator runtime <code>.dmg</code> file directly from Safari: the download was way faster for me, and it can be resumed if it fails (it did not for me but I checked, for science and for you 😘).</p>
<p>Once you have downloaded the simulator file, add it to Xcode with this command (make sure to check the file path):</p>
<div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xcrun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> simctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> runtime</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iOS_17.0.1_Simulator_Runtime.dmg</span></span></code></pre>
</div><p>The command should take around 20 seconds to run. Now you can open Spotlight and type <em>Simulator</em>.</p>
<p><abbr title="We own the other team">w00t</abbr>! Another dumb mystery that should not exist is now solved. 💁‍♂️</p>
<h2 id="what-are-xcode-simulators-by-the-way" tabindex="-1">What are Xcode Simulators, by the way? <a class="header-anchor" href="#what-are-xcode-simulators-by-the-way" aria-label="Permalink to &quot;What are Xcode Simulators, by the way?&quot;">&ZeroWidthSpace;</a></h2>
<p>One of the benefits of using a Macbook is that you get access to Simulators for the whole Apple ecosystem through Xcode.</p>
<p>Simulators allow you to test how your websites or in-development native apps behave on any iPhone and iPad without the need to purchase the devices. Using Simulators doesn’t replace the experience of having your fingers on the real devices, but they are very good and you should definitely use them.</p>
<picture>
    <source media="(prefers-color-scheme: dark)" srcset="/content/simulator-mehdi-blog-dark.webp" type="image/webp"/>
    <source media="(prefers-color-scheme: dark)" srcset="/content/simulator-mehdi-blog-dark.png" type="image/png"/>
    <source media="(prefers-color-scheme: light)" srcset="/content/simulator-mehdi-blog-light.webp" type="image/webp" />
    <img src="/content/simulator-mehdi-blog-light.png" alt="Simulator showing an iPhone SE (3rd generation) using iOS 17.0. The About page of Mehdi Merah’s blog is displayed in Safari." width="400" style="margin-inline: auto;" />
</picture>
<p>Simulators are handy, robust and come with a good set of features, like the ability to use Safari macOS developer tools to inspect any Safari iOS tab. There’s an Apple WWDC 23 video about it (<a href="https://developer.apple.com/videos/play/wwdc2023/10262?time=402" target="_blank" rel="noreferrer">from 6:42</a> to 8:45).</p>
<details class="details custom-block"><summary>Screenshot of Safari iOS inspected from macOS.</summary>
<picture>
    <source media="(prefers-color-scheme: dark)" srcset="/content/ios-simulator-safari-dark.webp" type="image/webp"/>
    <source media="(prefers-color-scheme: dark)" srcset="/content/ios-simulator-safari-dark.png" type="image/png"/>
    <source media="(prefers-color-scheme: light)" srcset="/content/ios-simulator-safari-light.webp" type="image/webp"/>
    <img src="/content/ios-simulator-safari-light.png" alt="Simulator showing an iPhone SE (3rd generation) using iOS 17.0. Next to its window is Safari macOS developer tools, with the element and styles panes visible." style="margin-inline: auto;" />
</picture>
<p>View this screenshot in light mode (<a href="/content/ios-simulator-safari-light.webp">WebP</a>, <a href="/content/ios-simulator-safari-light.png">PNG</a>) or in dark mode (<a href="/content/ios-simulator-safari-dark.webp">WebP</a>, <a href="/content/ios-simulator-safari-dark.png">PNG</a>).</p>
</details>
<h2 id="updates-to-this-article" tabindex="-1">Updates to this article <a class="header-anchor" href="#updates-to-this-article" aria-label="Permalink to &quot;Updates to this article&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="" tabindex="-1"><datetime date="2025-02-07" formatter="longdate"/> <a class="header-anchor" href="#" aria-label="Permalink to &quot;&lt;datetime date=&quot;2025-02-07&quot; formatter=&quot;longdate&quot;/&gt;&quot;">&ZeroWidthSpace;</a></h3>
<ul>
<li>I now have fast internet and have noticed a slow bandwidth isn’t the only issue to download the Simulator runtime: the download speed is throttled anyway for <a href="#fail-the-traditional-way">failing</a> <a href="#fail-same-player-try-again-with-the-command-line">situations</a>.</li>
<li>Bad news: <a href="#success-use-safari">the only successful possibility</a> got killed: Apple does’t provide new iOS Simulator runtimes as direct download on their website since iOS 18.3. I still have to try to download it, pending a macOS computer update before I do. Previous runtimes (iOS 18.2 and below) are still available and fast to download.</li>
</ul>
<p>If you want to share how this change impacts you, follow the website footer and tell me.</p>
]]></content:encoded>
            <author>hi@mehdi.cc (Mehdi Merah)</author>
        </item>
        <item>
            <title><![CDATA[Recover a lost Git stash in two steps]]></title>
            <link>https://blog.mehdi.cc/articles/recover-a-lost-git-stash</link>
            <guid isPermaLink="false">https://blog.mehdi.cc/articles/recover-a-lost-git-stash</guid>
            <pubDate>Wed, 15 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A technique to retrieve lost stashes from the limbos.]]></description>
            <content:encoded><![CDATA[<p>Losing in-progress work is one of the fastest ways to feel helpless in front of a computer. This article about recovering lost <a href="https://css-irl.info/how-git-stash-can-help-you-juggle-multiple-branches" target="_blank" rel="noreferrer">git stashes</a> was <a href="https://dev.to/meduzen/recover-a-lost-git-stash-in-two-steps-569" target="_blank" rel="noreferrer">initially published</a> 5 years ago on <em>Dev.to</em> and has a steady 10000 views per year, so I guess it happens to a lot of persons.</p>
<hr>
<h1 id="recover-a-lost-git-stash-in-two-steps" tabindex="-1">Recover a lost Git stash in two steps <a class="header-anchor" href="#recover-a-lost-git-stash-in-two-steps" aria-label="Permalink to &quot;Recover a lost Git stash in two steps&quot;">&ZeroWidthSpace;</a></h1>
<datetime :date="$frontmatter.publishedAt" formatter="longdate"/><tags/><p>Sometimes you end up <a href="https://css-irl.info/how-git-stash-can-help-you-juggle-multiple-branches" target="_blank" rel="noreferrer">stashing</a> <a href="https://www.git-scm.com/docs/git-stash" target="_blank" rel="noreferrer">code</a>, then at some point those stashes get cleaned. And one day, you may be like:</p>
<blockquote>
<p>Ooops, I think I just deleted stashes that I needed! Are they lost forever? 😨</p>
</blockquote>
<p>Fortunately, I managed to recover them. Here’s the two-steps procedure that worked for me after going <a href="https://stackoverflow.com/questions/89332/how-to-recover-a-dropped-stash-in-git" target="_blank" rel="noreferrer">through</a> <a href="https://stackoverflow.com/questions/32517870/how-to-undo-git-stash-clear" target="_blank" rel="noreferrer">various</a> <a href="https://stackoverflow.com/questions/20537223/when-should-i-use-git-stash" target="_blank" rel="noreferrer">readings</a> (and others), and also some general tips.</p>
<h2 id="step-1-list-lost-stashes" tabindex="-1">Step 1: list lost stashes <a class="header-anchor" href="#step-1-list-lost-stashes" aria-label="Permalink to &quot;Step 1: list lost stashes&quot;">&ZeroWidthSpace;</a></h2>
<p>In your project where stashes are trashed:</p>
<div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> fsck</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --unreachable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> commit</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> cut</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ' '</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f3</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xargs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> log</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --merges</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --no-walk</span></span></code></pre>
</div><p>It returns a list of lost stashes, ordered by date.</p>
<div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">commit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 7754fed19955d2958d952ab2836b22631b036b5</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Merge:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> f0a125c</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 36f580</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> e508036</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Author:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Mehdi</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Merah</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">xx@xxx.co</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">m</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Date:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Fri</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Apr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 27</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 16:41:17</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2018</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +0200</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    On</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> regions-components:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> After</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Regions</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> component</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">commit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 43c45c94caadcc87d783064624585c194f4be8</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Merge:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 13703fd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bc8220e</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4005629</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Author:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Mehdi</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Merah</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">xx@xxx.co</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">m</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Date:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Sat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Apr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 21</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 20:03:44</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2018</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +0200</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    On</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> master:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Before</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Regions</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> components</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">commit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bb615e44ed99b5a5622ead0c4bbb7b4acc19767</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Merge:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> c6a7b3</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 127203e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 45dcc54</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Author:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Mehdi</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Merah</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">xx@xxx.co</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">m</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Date:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Sat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Apr</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 7</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 19:39:50</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2018</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +0200</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    On</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> master:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Wololo</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># So, basically this anatomy:</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">commit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> {stash</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> commit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hash}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Merge:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> {parent</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> commit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hash…}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Author:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> {author</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name}</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">{author</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> email</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Date:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   {stash</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> date</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> with</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> timezone}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    On</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> {branch</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name}:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> {stash</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> commit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> message}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">END</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
</div><ul>
<li>To quit the list of stashes, press the <kbd>Q</kbd> key.</li>
<li>To navigate in a long stashes list, use the <kbd>up</kbd> and <kbd>down</kbd> arrows.</li>
<li>For Windows user, maybe <a href="https://dev.to/johnwait/comment/k9j5" target="_blank" rel="noreferrer">johnwait’s comment</a> will help you during the battle:</li>
</ul>
<details class="details custom-block"><summary>johnwait’s comment</summary>
<blockquote>
<p>On Windows, in a good old command window (your usual <code>cmd.exe</code>), step 1. could be translated to:</p>
<p><code>for /f &quot;tokens=3&quot; %a in ('git fsck --unreachable ^| find &quot;commit&quot;') do @git log --merges --no-walk %a</code></p>
<p>If your Git speaks français, and you're one to choose the more complicated path, you could use something like:</p>
<p><code>for /f &quot;tokens=1,2,3,4&quot; %a in ('git fsck --unreachable ^| find &quot;commit&quot;') do @if &quot;%c&quot;==&quot;inatteignable&quot; (@git log --merges --no-walk %d) else (@git log --merges --no-walk %c)</code></p>
<p>or, really, keep it simple with <code>alias git='LANG=en_GB git'</code> instead.</p>
<p><strong>Bonus</strong></p>
<p>For PowerShell aficionados, here's a command that should work regardless of your Git's locale (well, as long as a commit is still referred to as <code>commit</code>):</p>
<p><code>(git fsck --unreachable | Select-String &quot;commit&quot;) -split '\s+' | Select-String -pattern &quot;^[0-9a-fA-F]{40}$&quot; | ForEach-Object { git log --merges --no-walk $_ }</code></p>
<p>(Really useful post btw!)</p>
</blockquote>
</details>
<h2 id="step-2-send-a-lost-stash-back-where-it-comes-from" tabindex="-1">Step 2: send a lost stash back where it comes from <a class="header-anchor" href="#step-2-send-a-lost-stash-back-where-it-comes-from" aria-label="Permalink to &quot;Step 2: send a lost stash back where it comes from&quot;">&ZeroWidthSpace;</a></h2>
<p>Let’s use the commit hash of the second stash (from the previous list):</p>
<div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> update-ref</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> refs/stash</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 4b3fc45c94caadcc87d783064624585c194f4be8</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "My recovered stash"</span></span></code></pre>
</div><p>And that’s it! You’ll find your stash as usual, using <code>git stash list</code> or by having a look in your <a href="http://gitup.co/" target="_blank" rel="noreferrer">favorite Git client</a>.</p>
<h2 id="gotchas" tabindex="-1">Gotchas <a class="header-anchor" href="#gotchas" aria-label="Permalink to &quot;Gotchas&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="_1-i-still-can-t-see-my-recovered-stash" tabindex="-1">1. I still can’t see my recovered stash <a class="header-anchor" href="#_1-i-still-can-t-see-my-recovered-stash" aria-label="Permalink to &quot;1. I still can’t see my recovered stash&quot;">&ZeroWidthSpace;</a></h3>
<p>Retry using the <code>--create-reflog</code> parameter (thanks <a href="https://dev.to/studoggithub/comment/d54a" target="_blank" rel="noreferrer">studoggithub</a>):</p>
<div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> update-ref</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> refs/stash</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 4b3fc45c94caadcc87d783064624585c194f4be8</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --create-reflog</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "My recovered stash"</span></span></code></pre>
</div><p>It did the trick for that person using git 2.22.0 on Ubuntu 18.04.</p>
<h3 id="_2-my-git-client-isn-t-in-english" tabindex="-1">2. My Git client isn’t in English <a class="header-anchor" href="#_2-my-git-client-isn-t-in-english" aria-label="Permalink to &quot;2. My Git client isn’t in English&quot;">&ZeroWidthSpace;</a></h3>
<p>If your Git isn’t in English, you’ll have to run <code>alias git='LANG=en_GB git'</code> each time you want to recover a set of stashes (thanks <a href="https://dev.to/mathieuschopfer/comment/egd0" target="_blank" rel="noreferrer">mathieuschopfer</a>), otherwise the <code>unreachable</code> flag in <code>git fsck --unreachable</code> might be in another language, so you can either:</p>
<ul>
<li>alias the language like Mathieu (put in in your <code>.bashrc</code> file so it becomes permanent);</li>
<li>adapt the <a href="#step-1-list-lost-stashes">first command</a>: translate <code>unreachable</code> into your git language and update the <a href="https://man.openbsd.org/cut.1" target="_blank" rel="noreferrer"><code>cut</code> command</a>.</li>
</ul>
<h2 id="advices" tabindex="-1">Advices <a class="header-anchor" href="#advices" aria-label="Permalink to &quot;Advices&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="commit-messages-are-healthy" tabindex="-1">Commit messages are healthy <a class="header-anchor" href="#commit-messages-are-healthy" aria-label="Permalink to &quot;Commit messages are healthy&quot;">&ZeroWidthSpace;</a></h3>
<p>I <strong>always add a commit message</strong> to stash using <code>git stash save -m &quot;My commit message&quot;</code>: without message, the only way to identify a stash are its timestamp and the branch it was saved from, which might not be enough compared to human-written words.</p>
<p>Commit messages also help Git clients:</p>
<ul>
<li><a href="https://gitup.co" target="_blank" rel="noreferrer">GitUp</a>, the Git client I use, completely fails at showing unnamed stashes. That’s probably why you can’t create a stash in GitUp without giving it a name, which is great!</li>
<li>The well-known <a href="https://www.sourcetreeapp.com/" target="_blank" rel="noreferrer">SourceTree</a> succeeds at showing unnamed stashes, but as you can guess, the list isn’t friendly to browse: <img src="/content/sourcetree-stash-list-2018.png" alt="Unnamed stashes in SourceTree"></li>
</ul>
<h3 id="prefer-git-stash-apply-over-git-stash-pop" tabindex="-1">Prefer <code>git stash apply</code> over <code>git stash pop</code> <a class="header-anchor" href="#prefer-git-stash-apply-over-git-stash-pop" aria-label="Permalink to &quot;Prefer `git stash apply` over `git stash pop`&quot;">&ZeroWidthSpace;</a></h3>
<p>Unlike <code>git stash pop</code>, <code>git stash apply</code> does not remove the stash from the list of stashes, which can avoid accidental loss.</p>
<h3 id="prefer-branches-over-stashes" tabindex="-1">Prefer branches over stashes <a class="header-anchor" href="#prefer-branches-over-stashes" aria-label="Permalink to &quot;Prefer branches over stashes&quot;">&ZeroWidthSpace;</a></h3>
<p>Stashes serve a different purpose than branches. Wherever it makes sense to you, commit your code to a new branch instead of stashing it. You’re gonna start a new branch anyway if you use a branching model like <a href="http://nvie.com/posts/a-successful-git-branching-model/" target="_blank" rel="noreferrer">Git Flow</a>.</p>
]]></content:encoded>
            <author>hi@mehdi.cc (Mehdi Merah)</author>
        </item>
        <item>
            <title><![CDATA[Using Vitepress `cleanUrls` option on a Nginx server]]></title>
            <link>https://blog.mehdi.cc/articles/vitepress-cleanurls-on-nginx-environment</link>
            <guid isPermaLink="false">https://blog.mehdi.cc/articles/vitepress-cleanurls-on-nginx-environment</guid>
            <pubDate>Fri, 10 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[We share, receive and see URLs all the time, so it’s nice to have human-readable URLs. Here’s my Nginx configuration for this in Vitepress.]]></description>
            <content:encoded><![CDATA[<p>We share, receive and see URLs all the time, so it’s nice to have <a href="https://en.wikipedia.org/wiki/Clean_URL" target="_blank" rel="noreferrer">human-readable URLs</a> like <code>example.tld/yeah</code> instead of <code>example.tld/yeah.html</code>.</p>
<p>On this blog using <a href="https://vitepress.dev" target="_blank" rel="noreferrer">Vitepress</a> on a <a href="https://nginx.org/" target="_blank" rel="noreferrer">Nginx</a> environment, it works rather well (with a minor trade-off).</p>
<hr>
<h1 id="vitepress-cleanurls-option-on-a-nginx-server" tabindex="-1">Vitepress <code>cleanUrls</code> option on a Nginx server <a class="header-anchor" href="#vitepress-cleanurls-option-on-a-nginx-server" aria-label="Permalink to &quot;Vitepress `cleanUrls` option on a Nginx server&quot;">&ZeroWidthSpace;</a></h1>
<datetime :date="$frontmatter.publishedAt" formatter="longdate"/><tags/><p>Setting Vitepress <a href="https://vitepress.dev/reference/site-config#cleanurls" target="_blank" rel="noreferrer"><code>cleanUrls</code></a> to <code>true</code> removes the trailing <code>.html</code> from URLs. The internal links of your Vitepress app changes <strong>from <code>&lt;a href=&quot;/something.html&quot;&gt;</code> to <code>&lt;a href=&quot;/something&quot;&gt;</code></strong>.</p>
<p>However, <code>cleanUrls</code> does not change the file names or the folder structure of the generated HTML files, which means your server must now be able to serve the <code>/something.html</code> file when the <code>/something</code> URL is requested.</p>
<p>Here’s how I solved it, assuming the app is hosted on its own domain or subdomain (<code>example.tld</code> or <code>subdomain.example.tld</code>, I did not test <code>example.tld/path/to/app</code>).</p>
<p>At the time of writing, I’m using Vitepress 1.0.0-rc.24 and Nginx 1.25.</p>
<h2 id="full-configuration" tabindex="-1">Full configuration <a class="header-anchor" href="#full-configuration" aria-label="Permalink to &quot;Full configuration&quot;">&ZeroWidthSpace;</a></h2>
<p>If you don’t want to read the <a href="#step-by-step">step-by-step section</a>, here are the important parts of the configuration. Adapt them to your needs and make sure to understand the <a href="#the-trade-off-for-index-pages">trade-off for index pages</a>.</p>
<div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    index </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">index.html;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    rewrite</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF"> ^(.+)/$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">permanent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ($request_uri </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~ </span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">^/(.*)index\.html(\?|$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /$1;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ($request_uri </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~ </span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">^/(.*)\.html(\?|$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /$1;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        error_page </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /404.html;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        try_files </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$uri $uri.html $uri/ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">=404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><details class="details custom-block"><summary>The same, with code comments:</summary>
<div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    index </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">index.html;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # and other things…</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # Remove the trailing slash (permanent 301 redirect). </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    rewrite</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF"> ^(.+)/$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">permanent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # Remove the trailing `index.html`. </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ($request_uri </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~ </span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">^/(.*)index\.html(\?|$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /$1;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # Remove the trailing `.html`. </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ($request_uri </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~ </span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">^/(.*)\.html(\?|$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /$1;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # When the HTTP status code is 404, answer with the `/404.html` file.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        error_page </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /404.html;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # When `foo/bar` (which is `$uri`) is requested, </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # try to serve the first existing file among the list: </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # `foo/bar`, `foo/bar.html` or `foo/bar/index.html`. </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # Otherwise answer with a 404 code.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        try_files </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$uri $uri.html $uri/ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">=404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div></details>
<h2 id="step-by-step" tabindex="-1">Step-by-step <a class="header-anchor" href="#step-by-step" aria-label="Permalink to &quot;Step-by-step&quot;">&ZeroWidthSpace;</a></h2>
<p>The <a href="#step-1-the-gist">first step</a> is enough to have clean URLs, the <a href="#step-2-the-404-page">other steps after</a> will bring refinements and safeguards.</p>
<h3 id="step-1-the-gist" tabindex="-1">Step 1: the gist <a class="header-anchor" href="#step-1-the-gist" aria-label="Permalink to &quot;Step 1: the gist&quot;">&ZeroWidthSpace;</a></h3>
<p>Make sure your Nginx <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#location" target="_blank" rel="noreferrer"><code>location</code></a> block have this <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files" target="_blank" rel="noreferrer"><code>try_files</code></a> rule:</p>
<div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes github-light github-dark has-highlighted vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    index </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">index.html;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # and other things…</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # When `foo/bar` (which is `$uri`) is requested, </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # try to serve the first existing file among the list: </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # `foo/bar`, `foo/bar.html` or `foo/bar/index.html`. </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # Otherwise answer with a 404 code.</span></span>
<span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        try_files </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$uri $uri.html $uri/ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">=404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><details class="details custom-block"><summary>The <code>try_files</code> list in details:</summary>
<ul>
<li><strong><code>$uri</code></strong> is for <strong>exact matches</strong> like assets: when the browser requests <code>logo.svg</code>, we search for a <code>logo.svg</code> file.</li>
<li><strong><code>$uri.html</code></strong> <strong>adds a missing <code>.html</code></strong> (<code>foo/bar</code> becomes <code>foo/bar.html</code>), which is the reverse operation of Vitepress <code>cleanUrls</code>.</li>
<li><strong><code>$uri/</code></strong> adds a trailing <code>/</code> (<code>foo/bar/</code>) to instruct Nginx to “look into the <code>foo/bar</code> <strong>directory</strong>”, so Nginx ends up searching for <code>foo/bar/index.html</code>, as defined by the <a href="https://nginx.org/en/docs/http/ngx_http_index_module.html#index" target="_blank" rel="noreferrer"><code>index</code></a> directive. This one is needed for the root of the website (<code>example.tld</code> must serve <code>example.tld/index.html</code>).</li>
<li><strong><code>=404</code></strong> throws a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404" target="_blank" rel="noreferrer">404</a> status if none of these files can be found.</li>
</ul>
</details>
<p>At this step, most paths are working with and without <code>.html</code>:</p>
<table tabindex="0">
<thead>
<tr>
<th>Markdown</th>
<th>Valid paths</th>
<th>Invalid paths</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/index.md</code></td>
<td><code>/</code>, <code>/index.html</code></td>
<td>-</td>
</tr>
<tr>
<td><code>/notes.md</code></td>
<td><code>/notes</code>, <code>/notes.html</code></td>
<td><code>/notes/</code> (<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403" target="_blank" rel="noreferrer">403</a>)</td>
</tr>
<tr>
<td><code>/notes/my-note.md</code></td>
<td><code>/notes/my-note</code>, <code>/notes/my-note/</code>, <code>/notes/my-note.html</code></td>
<td>-</td>
</tr>
<tr>
<td>-</td>
<td>anything not found</td>
<td>Nginx 404</td>
</tr>
</tbody>
</table>
<p>We need 3 improvements:</p>
<ul>
<li>Vitepress 404 page (see <a href="#step-2-the-404-page">step 2</a>) instead of Nginx 404 message;</li>
<li>a fix for the 403 when there’s a trailing slash (<a href="#step-3-avoiding-the-403-errors">step 3</a>);</li>
<li>no more paths with <code>.html</code> (<a href="#step-4-redirect-slughtml-to-slug">step 4</a>);</li>
</ul>
<h3 id="step-2-the-404-page" tabindex="-1">Step 2: the 404 page <a class="header-anchor" href="#step-2-the-404-page" aria-label="Permalink to &quot;Step 2: the 404 page&quot;">&ZeroWidthSpace;</a></h3>
<p>The default Vitepress comes with a <code>404.html</code> page. Let’s serve it using the <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page" target="_blank" rel="noreferrer"><code>error_page</code></a> directive:</p>
<div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes github-light github-dark has-diff vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    index </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">index.html;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # and other things…</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # When the HTTP status code is 404, answer with the `/404.html` file. #</span></span>
<span class="line diff add"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        error_page </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /404.html; </span></span>
<span class="line diff add"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        try_files </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$uri $uri.html $uri/ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">=404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><h3 id="step-3-avoiding-the-403-errors" tabindex="-1">Step 3: avoiding the 403 errors <a class="header-anchor" href="#step-3-avoiding-the-403-errors" aria-label="Permalink to &quot;Step 3: avoiding the 403 errors&quot;">&ZeroWidthSpace;</a></h3>
<p>To avoid the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403" target="_blank" rel="noreferrer">403</a> on paths with a trailing slash (e.g. <code>my-blog.com/</code> or <code>my-blog.com/notes/</code>), we can redirect them to their non-trailing slash version using the <a href="https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite" target="_blank" rel="noreferrer"><code>rewrite</code></a> directive in the <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#server" target="_blank" rel="noreferrer"><code>server</code></a> block.</p>
<div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes github-light github-dark has-diff vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    index </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">index.html;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # and other things…</span></span>
<span class="line diff add"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # Remove the trailing slash (permanent 301 redirect). #</span></span>
<span class="line diff add"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    rewrite</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF"> ^(.+)/$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">permanent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        error_page </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /404.html;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        try_files </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$uri $uri.html $uri/ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">=404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><div class="warning custom-block" id="warning-http-redirect"><p class="custom-block-title">WARNING</p>
<p>Note that the <code>permanent</code> keyword does a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301" target="_blank" rel="noreferrer">301</a> redirect, which is permanent and might be cached during a very long time by browsers and search engines. If you are not sure about this rewriting rule, consider the <code>redirect</code> keyword to get a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302" target="_blank" rel="noreferrer">302</a> (temporary) redirect instead of a 301.</p>
</div>
<h3 id="step-4-redirect-slug-html-to-slug" tabindex="-1">Step 4: redirect <code>/slug.html</code> to <code>/slug</code> <a class="header-anchor" href="#step-4-redirect-slug-html-to-slug" aria-label="Permalink to &quot;Step 4: redirect `/slug.html` to `/slug`&quot;">&ZeroWidthSpace;</a></h3>
<p>For now, <code>/slug</code> and <code>/slug.html</code> are both working, so let’s redirect the one with the trailing <code>.html</code>.</p>
<p><a href="https://stackoverflow.com/questions/38228393/nginx-remove-html-extension" target="_blank" rel="noreferrer">https://stackoverflow.com/questions/38228393/nginx-remove-html-extension</a></p>
<div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes github-light github-dark has-diff vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # other things…</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    rewrite</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF"> ^(.+)/$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">permanent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line diff add"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # Remove the trailing `index.html`. #</span></span>
<span class="line diff add"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ($request_uri </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~ </span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">^/(.*)index\.html(\?|$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) { </span></span>
<span class="line diff add"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /$1; </span></span>
<span class="line diff add"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span></span>
<span class="line diff add"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # Remove the trailing `.html`. #</span></span>
<span class="line diff add"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ($request_uri </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~ </span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">^/(.*)\.html(\?|$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)) { </span></span>
<span class="line diff add"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /$1; </span></span>
<span class="line diff add"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    location</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        error_page </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /404.html;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        try_files </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$uri $uri.html $uri/ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">=404</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><div class="warning custom-block"><p class="custom-block-title">WARNING</p>
<p>Like explained in the <a href="#warning-http-redirect">warning of the previous step</a>, you might prefer <code>return 302</code> over <code>return 301</code>.</p>
</div>
<h2 id="the-trade-off-for-index-pages" tabindex="-1">The trade-off for index pages <a class="header-anchor" href="#the-trade-off-for-index-pages" aria-label="Permalink to &quot;The trade-off for index pages&quot;">&ZeroWidthSpace;</a></h2>
<p>If you have index pages like <code>notes/index.md</code>, you have to move them to <code>notes.md</code>, otherwise you’ll experience an infinite redirection loop where Nginx wants to remove the slash, but Vitepress wants to add it back.</p>
<p>In other words, do this:</p>
<div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>.</span></span>
<span class="line"><span>├─ index.md</span></span>
<span class="line"><span>├─ notes</span></span>
<span class="line"><span>│  ├─ index.md // [!code --]</span></span>
<span class="line"><span>│  ├─ post-1.md</span></span>
<span class="line"><span>│  └─ post-2.md</span></span>
<span class="line"><span>├─ about.md</span></span>
<span class="line"><span>└─ notes.md // [!code ++]</span></span></code></pre>
</div><h2 id="readings" tabindex="-1">Readings <a class="header-anchor" href="#readings" aria-label="Permalink to &quot;Readings&quot;">&ZeroWidthSpace;</a></h2>
<p>Aside from <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html" target="_blank" rel="noreferrer">Nginx documentation for the HTTP core module</a>, I was helped by:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/38228393/nginx-remove-html-extension" target="_blank" rel="noreferrer">NGINX remove .html extension</a> (StackOverflow)</li>
<li><a href="https://github.com/vuejs/vitepress/discussions/2837#discussioncomment-7337266" target="_blank" rel="noreferrer">Routing access failure after server Nginx deployment</a> (GitHub issue on Vitepress repository): this was where I started before deciding to write this post.</li>
</ul>
]]></content:encoded>
            <author>hi@mehdi.cc (Mehdi Merah)</author>
        </item>
    </channel>
</rss>