<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David JULIEN</title>
    <description>The latest articles on DEV Community by David JULIEN (@davidjulien).</description>
    <link>https://dev.to/davidjulien</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3846476%2F30dfe6c1-a6d0-4853-b93c-e0e69028efb9.png</url>
      <title>DEV Community: David JULIEN</title>
      <link>https://dev.to/davidjulien</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davidjulien"/>
    <language>en</language>
    <item>
      <title>Treating blog posts like production code</title>
      <dc:creator>David JULIEN</dc:creator>
      <pubDate>Fri, 27 Mar 2026 18:00:00 +0000</pubDate>
      <link>https://dev.to/davidjulien/treating-blog-posts-like-production-code-4koo</link>
      <guid>https://dev.to/davidjulien/treating-blog-posts-like-production-code-4koo</guid>
      <description>&lt;p&gt;When I start a new project, I immediately set up a repository, linters, tests, git hooks, and CI. Not because I'm forced to, but because fast feedback loops make me a better developer.&lt;/p&gt;

&lt;p&gt;When I decided to start this blog, I asked myself: why should writing be any different?&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository setup
&lt;/h2&gt;

&lt;p&gt;Go to your GitHub account (&lt;code&gt;https://github.com/&amp;lt;YOUR_ACCOUNT&amp;gt;?tab=repositories&lt;/code&gt;) and create a new repository named &lt;code&gt;blog&lt;/code&gt; (for example).&lt;/p&gt;

&lt;p&gt;Then, set it up with the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;blog

&lt;span class="nb"&gt;cd &lt;/span&gt;blog

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# blog"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; README.md

git init
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git add README.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit"&lt;/span&gt;
git remote add origin https://github.com/davidjulien/blog.git
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Linters
&lt;/h2&gt;

&lt;p&gt;Linters ensure that articles are well-formatted and consistent.&lt;/p&gt;

&lt;p&gt;Here are the linters I set up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/markdownlint/markdownlint" rel="noopener noreferrer"&gt;&lt;code&gt;markdownlint&lt;/code&gt;&lt;/a&gt; to check Markdown files and flag style issues.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vale.sh/" rel="noopener noreferrer"&gt;&lt;code&gt;vale&lt;/code&gt;&lt;/a&gt; for writing style.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ltex-plus/ltex-ls-plus" rel="noopener noreferrer"&gt;&lt;code&gt;ltex-ls-plus&lt;/code&gt;&lt;/a&gt; to check for grammar and spelling mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lycheeverse/lychee" rel="noopener noreferrer"&gt;&lt;code&gt;lychee&lt;/code&gt;&lt;/a&gt; to check that the links in my articles are valid.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Checking Markdown files with markdownlint-cli2
&lt;/h3&gt;

&lt;p&gt;Since I use Markdown for my articles, I want to ensure that they're well-formatted and consistent. For that, I use &lt;code&gt;markdownlint-cli2&lt;/code&gt;, which is a command-line interface for &lt;code&gt;markdownlint&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also try &lt;a href="https://github.com/igorshubovych/markdownlint-cli" rel="noopener noreferrer"&gt;&lt;code&gt;markdownlint-cli&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I chose &lt;code&gt;markdownlint-cli2&lt;/code&gt; for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jsonc&lt;/code&gt; for configuration, which allows me to add comments and explanations in the configuration file.&lt;/li&gt;
&lt;li&gt;Written by the &lt;code&gt;markdownlint&lt;/code&gt; author himself&lt;/li&gt;
&lt;li&gt;Has an official GitHub Action&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Setting up &lt;code&gt;markdownlint-cli2&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Install the tool as a development dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;markdownlint-cli2 &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And write &lt;code&gt;package.json&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:md"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"markdownlint-cli2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:md:fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"markdownlint-cli2 --fix"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"markdownlint-cli2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.21.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add a basic configuration file &lt;code&gt;.markdownlint-cli2.jsonc&lt;/code&gt; at the root of your repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/refs/heads/main/schema/markdownlint-cli2-config-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Only lint articles&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"globs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"articles/**/*.md"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I &lt;strong&gt;prefer starting with everything enabled, then turn off with intention.&lt;/strong&gt; Every rule you turn off should be a conscious decision, not a default.&lt;br&gt;
That applies beyond linting. Code reviews, architecture choices, dependency upgrades: start strict, relax where it makes sense, and know why.&lt;br&gt;
Don't forget to add comments in the configuration file to explain why you choose to enable or turn off certain rules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md" rel="noopener noreferrer"&gt;&lt;code&gt;markdownlint&lt;/code&gt; documentation&lt;/a&gt; lists available rules and their default configuration.&lt;/p&gt;

&lt;p&gt;In my case, I started with this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/refs/heads/main/schema/markdownlint-cli2-config-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Only lint articles&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"globs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"articles/**/*.md"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD003: Enforce ATX-style headings (e.g. "# Heading") for consistency&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"heading-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"atx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD004: Use dashes for unordered lists to keep a uniform style&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ul-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dash"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD025: Tells markdownlint not to treat the frontmatter title as an h1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"single-h1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"front_matter_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD026: Allow trailing punctuation in headings (e.g. "What happened?")&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"no-trailing-punctuation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"punctuation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".,;:!"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD029: Ordered list items must use incrementing numbers (1, 2, 3) for clarity&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ol-prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ordered"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD033: Disallow inline HTML (authorized elements can be added later)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"no-inline-html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"allowed_elements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD035: Use "---" for horizontal rules for consistency&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hr-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"---"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD040: Fenced code blocks must specify a language for syntax highlighting&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fenced-code-language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"language_only"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD046: Always use fenced style for code blocks (not indented)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code-block-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fenced"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD048: Use backticks (not tildes) for fenced code blocks&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code-fence-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"backtick"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD049: Use asterisks for emphasis (*italic*) for consistency&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"emphasis-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asterisk"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD050: Use asterisks for strong emphasis (**bold**) for consistency&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strong-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asterisk"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// MD055: Use pipes on both sides of table rows for readability&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"table-pipe-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"leading_and_trailing"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Running the linter
&lt;/h4&gt;

&lt;p&gt;First run to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run lint:md


&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; lint:md
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; markdownlint-cli2

markdownlint-cli2 v0.21.0 &lt;span class="o"&gt;(&lt;/span&gt;markdownlint v0.40.0&lt;span class="o"&gt;)&lt;/span&gt;
Finding: articles/&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.md
Linting: 1 file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
Summary: 7 error&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
articles/20260313-setup-a-repository-to-store-blog-articles.md:6 error MD025/single-title/single-h1 Multiple top-level headings &lt;span class="k"&gt;in &lt;/span&gt;the same document &lt;span class="o"&gt;[&lt;/span&gt;Context: &lt;span class="s2"&gt;"Treating blog posts like produ..."&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
articles/20260313-setup-a-repository-to-store-blog-articles.md:8:81 error MD013/line-length Line length &lt;span class="o"&gt;[&lt;/span&gt;Expected: 80&lt;span class="p"&gt;;&lt;/span&gt; Actual: 95]
articles/20260313-setup-a-repository-to-store-blog-articles.md:26:4 error MD009/no-trailing-spaces Trailing spaces &lt;span class="o"&gt;[&lt;/span&gt;Expected: 0 or 2&lt;span class="p"&gt;;&lt;/span&gt; Actual: 1]
articles/20260313-setup-a-repository-to-store-blog-articles.md:33 error MD032/blanks-around-lists Lists should be surrounded by blank lines &lt;span class="o"&gt;[&lt;/span&gt;Context: &lt;span class="s2"&gt;"- [markdownlint](https://githu..."&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works!&lt;/p&gt;

&lt;p&gt;Now I can analyze the errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The main error is &lt;code&gt;MD013/line-length&lt;/code&gt;.
This rule checks that lines don't exceed a certain length (80 characters by default).
I can either break the long line or increase the line length limit in the configuration file or disable the rule if I don't care about it.
I decided to disable this rule.
Even if I prefer shorter lines and try to write one phrase per line, this rule is annoying when you have a long URL or you are just over the limit.
I prefer to set a visual clue in my editor when I reach 120 characters.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;MD&lt;/span&gt;&lt;span class="mi"&gt;013&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;limit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(useful&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;articles)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"line-length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Another error is &lt;code&gt;MD024/no-duplicate-heading&lt;/code&gt;.&lt;br&gt;
This rule checks that there are no duplicate headings in the same document.&lt;br&gt;
In my case, I had two &lt;code&gt;## Running the linter&lt;/code&gt; headings after I wrote the two first linter sections.&lt;br&gt;
I thought to disable this rule, but the documentation explained that "Some Markdown parsers generate anchors for headings based on the heading name; headings with the same content can cause problems with that."&lt;br&gt;
I decided to keep this rule and change my headings to be more specific.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another cryptic error I hit is &lt;code&gt;MD025/single-title/single-h1&lt;/code&gt;.&lt;br&gt;
This rule checks that there is only one top-level heading in the document.&lt;br&gt;
In my case, I have an explicit one (a line starting with &lt;code&gt;#&lt;/code&gt;) and a hidden one (the &lt;code&gt;frontmatter&lt;/code&gt; title):&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;  ---&lt;/span&gt;
  title: "Treating blog posts like production code"
  tags: tooling, writing, vale, markdownlint
&lt;span class="p"&gt;  ---
&lt;/span&gt;
  # Treating blog posts like production code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want to keep both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// MD025: Tells markdownlint not to treat the frontmatter title as an h1 to keep the explicit one&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"single-h1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"front_matter_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After fixing errors and updating configuration, you can run the command again to check that everything is good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run lint:md


&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; lint:md
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; markdownlint-cli2

markdownlint-cli2 v0.21.0 &lt;span class="o"&gt;(&lt;/span&gt;markdownlint v0.40.0&lt;span class="o"&gt;)&lt;/span&gt;
Finding: articles/&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.md
Linting: 1 file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
Summary: 0 error&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vale for writing style
&lt;/h3&gt;

&lt;p&gt;Now that my article is well-formatted, I want to check that it's well-written.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting up &lt;code&gt;vale&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Install the tool with your package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;vale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create a configuration file &lt;code&gt;.vale.ini&lt;/code&gt; at the root of your repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;StylesPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;.vale/styles&lt;/span&gt;
&lt;span class="py"&gt;Vocab&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;blog&lt;/span&gt;
&lt;span class="py"&gt;MinAlertLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;suggestion&lt;/span&gt;
&lt;span class="py"&gt;Packages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Google, proselint, write-good, Readability, alex&lt;/span&gt;

&lt;span class="nn"&gt;[*.md]&lt;/span&gt;
&lt;span class="py"&gt;BasedOnStyles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Vale, Google, proselint, write-good, Readability, alex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose these packages for the following reasons:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Google&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Word choice, headings, contractions. Based on Google's Developer Style Guide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proselint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clichés, redundancy, jargon. Advice aggregated from great editors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;write-good&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Passive voice, weasel words, wordy phrases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Readability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Flesch-Kincaid&lt;/code&gt;, &lt;code&gt;SMOG&lt;/code&gt;. Are your sentences too dense?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Flags insensitive or inconsiderate language: gendered terms, ableist language, racial bias. &lt;code&gt;alex&lt;/code&gt; catches things you might not think about as a non-native English speaker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run this command to download the packages and to create the &lt;code&gt;styles&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vale &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create the vocabularies for your blog and add custom words to allow or flag in your writing style.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .vale/styles/config/vocabularies/blog
&lt;span class="nb"&gt;touch&lt;/span&gt; .vale/styles/config/vocabularies/blog/accept.txt
&lt;span class="nb"&gt;touch&lt;/span&gt; .vale/styles/config/vocabularies/blog/reject.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Running &lt;code&gt;vale&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vale articles/20260213-setup-a-repository-to-store-blog-articles.md

 articles/20260213-setup-a-repository-to-store-blog-articles.md
&lt;span class="o"&gt;[&lt;/span&gt;...]
✖ 13 errors, 29 warnings and 17 suggestions &lt;span class="k"&gt;in &lt;/span&gt;1 file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well… I knew that my writing style wasn't perfect, but I didn't expect that many warnings and suggestions!&lt;/p&gt;

&lt;p&gt;Now I need to review each error and decide: fix or ignore?&lt;/p&gt;

&lt;p&gt;I check the most common errors.&lt;br&gt;
&lt;code&gt;vale&lt;/code&gt; doesn't have a built-in way to do that.&lt;br&gt;
I use &lt;code&gt;jq&lt;/code&gt; to parse the JSON output and count the occurrences of each rule:&lt;/p&gt;

&lt;p&gt;Install &lt;code&gt;jq&lt;/code&gt; with your package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vale &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;JSON articles/ | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[][].Check'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
  23 Google.FirstPerson
  13 write-good.E-Prime
   5 Google.EmDash
   4 Google.Parens
   3 alex.ProfanityUnlikely
   3 Vale.Spelling
   2 write-good.Weasel
   2 Google.WordList
   2 Google.Exclamation
   1 write-good.TooWordy
   1 Readability.SMOG
   1 Readability.FleschReadingEase
   1 Readability.ColemanLiau
   1 proselint.Very
   1 proselint.Typography
   1 proselint.But
   1 Google.Will
   1 Google.We
   1 Google.Spacing
   1 Google.Ellipses
   1 Google.Acronyms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Google.FirstPerson&lt;/code&gt; I use a lot of first-person pronouns in my writing.
Since I write a personal blog, I don't care about this rule.
I can disable it in the configuration file.
I also disable &lt;code&gt;Google.We&lt;/code&gt; for the same reason.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;  &lt;span class="c"&gt;# I'm writing a personal blog, not corporate docs
&lt;/span&gt;  &lt;span class="py"&gt;Google.FirstPerson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;NO&lt;/span&gt;
  &lt;span class="py"&gt;Google.We&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;NO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;write-good.E-Prime&lt;/code&gt;: this one is less clear to me. Fortunately, each rule has documentation:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .vale/styles/write-good/E-Prime.yml
  extends: existence
  message: &lt;span class="s2"&gt;"Try to avoid using '%s'."&lt;/span&gt;
  ignorecase: &lt;span class="nb"&gt;true
  &lt;/span&gt;level: suggestion
  tokens:
    - am
    - are
    - aren&lt;span class="s1"&gt;'t
    - be
    - been
    - being
    - he'&lt;/span&gt;s
    - here&lt;span class="s1"&gt;'s
    - here'&lt;/span&gt;s
    - how&lt;span class="s1"&gt;'s
    - i'&lt;/span&gt;m
    - is
    - isn&lt;span class="s1"&gt;'t
    - it'&lt;/span&gt;s
    - she&lt;span class="s1"&gt;'s
    - that'&lt;/span&gt;s
    - there&lt;span class="s1"&gt;'s
    - they'&lt;/span&gt;re
    - was
    - wasn&lt;span class="s1"&gt;'t
    - we'&lt;/span&gt;re
    - were
    - weren&lt;span class="s1"&gt;'t
    - what'&lt;/span&gt;s
    - where&lt;span class="s1"&gt;'s
    - who'&lt;/span&gt;s
    - you&lt;span class="s1"&gt;'re
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule flags all forms of "to be".&lt;br&gt;
  I don't find it useful for blog writing. I set it to &lt;code&gt;NO&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;  &lt;span class="py"&gt;write-good.E-Prime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;NO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Google.EmDash&lt;/code&gt;: The preceding &lt;code&gt;cat&lt;/code&gt; command displays the rule content.
I also want to know where the errors are in my article.
For that, I can use the &lt;code&gt;--output=JSON&lt;/code&gt; option to get the details of each error and use &lt;code&gt;jq&lt;/code&gt; to filter the errors:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nv"&gt;$ &lt;/span&gt;vale &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;JSON articles/ | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[][] | select(.Check == "Google.EmDash") | "\(.Line):\(.Span[0]) \(.Message)"'&lt;/span&gt;
  176:47 Don&lt;span class="s1"&gt;'t put a space before or after a dash.
  177:42 Don'&lt;/span&gt;t put a space before or after a dash.
  179:37 Don&lt;span class="s1"&gt;'t put a space before or after a dash.
  180:53 Don'&lt;/span&gt;t put a space before or after a dash.
  183:87 Don&lt;span class="s1"&gt;'t put a space before or after a dash.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check my articles, I need these commands often. Instead of copy/pasting it, I create three scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;bin

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bin/vstats &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash
# Show Vale warnings count per rule
# Usage: bin/vstats

vale --output=JSON articles/ | jq -r '.[][].Check' | sort | uniq -c | sort -rn
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x bin/vstats
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bin/vdef &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash
# Show a Vale rule definition
# Usage: bin/vdef write-good.E-Prime

pkg="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;%%.*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
rule="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;##*.&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
cat ".vale/styles/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkg&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;rule&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;.yml"
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bin/vgrep &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash
# Show occurrences of a Vale rule
# Usage: bin/vgrep Google.EmDash

vale --output=JSON articles/ | jq -r ".[][] | select(.Check == &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;) | &lt;/span&gt;&lt;span class="se"&gt;\"\(&lt;/span&gt;&lt;span class="sh"&gt;.Line):&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sh"&gt;.Span[0]) &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sh"&gt;.Message)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x bin/vdef bin/vgrep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bin/vdef Google.EmDash
extends: existence
message: &lt;span class="s2"&gt;"Don't put a space before or after a dash."&lt;/span&gt;
&lt;span class="nb"&gt;link&lt;/span&gt;: &lt;span class="s2"&gt;"https://developers.google.com/style/dashes"&lt;/span&gt;
nonword: &lt;span class="nb"&gt;true
&lt;/span&gt;level: error
action:
  name: edit
  params:
    - trim
    - &lt;span class="s2"&gt;" "&lt;/span&gt;
tokens:
  - &lt;span class="s1"&gt;'\s[—–]\s'&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;bin/vgrep Google.EmDash
183:87 Don&lt;span class="s1"&gt;'t put a space before or after a dash.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's easier.&lt;br&gt;
I continue to check my article and fix the errors one by one.&lt;br&gt;
I only comment those where I can suggest a new kind of fix.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;write-good.Weasel&lt;/code&gt;: Weasel words are vague qualifiers that sound meaningful but say nothing precise.
For an engineering blog, I want to be precise and avoid vague language.
I'll keep this rule and fix these errors.
After fixing, I agree that these sentences are better without these words.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Google.Parens&lt;/code&gt;: False positive — this triggers on Markdown link syntax. Not useful for articles. I set it to &lt;code&gt;NO&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Google.WordList&lt;/code&gt;: This rule identified words that aren't recommended by Google's style guide.
In my case, I used the word "disable" three times. It suggests replacing it with "turn off".
I'm not fully sure if it's better since I target technical readers and "disable" feels right in this context.
Using "turn off" is slightly awkward, it sounds like a light switch.
Since I can't define an exception for the word "disable", it's the occasion to learn how to write custom rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start by checking the rule definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;cat&lt;/span&gt; .vale/styles/Google/WordList.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create a custom rule by copying the existing one and modifying it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; .vale/styles/MyBlog
  &lt;span class="nb"&gt;cp&lt;/span&gt; .vale/styles/Google/WordList.yml .vale/styles/MyBlog/WordList.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can remove the word "disable" from my custom rule and keep the other words.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alex.ProfanityUnlikely&lt;/code&gt;: This rule flags words that readers might misinterpret as profane.
In my case, it flagged the example I used to describe &lt;code&gt;alex&lt;/code&gt; role. I don't want to change this example, so I'm ignoring this rule
for this specific case by adding an inline comment in the article:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;  &lt;span class="c"&gt;&amp;lt;!-- vale alex.ProfanityUnlikely = NO --&amp;gt;&lt;/span&gt;
  my text
  &lt;span class="c"&gt;&amp;lt;!-- vale alex.ProfanityUnlikely = YES --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Readability.FleschReadingEase&lt;/code&gt;: This rule calculates the &lt;code&gt;Flesch Reading Ease&lt;/code&gt; score of the whole text.
The value is for the whole text, not only the first line.
A higher score indicates more readable text.
My article was just below the expected score.
I improved this score by breaking long sentences into shorter ones, using simpler words, and avoiding passive voice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After discarding rules and fixing errors, I have the following stats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✔ 0 errors, 0 warnings and 0 suggestions &lt;span class="k"&gt;in &lt;/span&gt;1 file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I aim for zero warnings.&lt;br&gt;
If you start with 10 errors, you might miss a new error lost between two existing ones.&lt;br&gt;
If you start with 0 errors, any new error is immediately visible, and you can fix it right away.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Grammar and spelling mistakes
&lt;/h3&gt;

&lt;p&gt;When you aren't a native English speaker, it's common to make grammar and spelling mistakes without noticing them.&lt;/p&gt;

&lt;p&gt;I tried two different tools to catch these mistakes: &lt;code&gt;ltex-ls-plus&lt;/code&gt; and &lt;code&gt;harper&lt;/code&gt;. Even if &lt;code&gt;harper&lt;/code&gt; is faster, I find&lt;br&gt;
the result of &lt;code&gt;ltex-ls-plus&lt;/code&gt; more accurate, so I decided to keep it.&lt;/p&gt;
&lt;h4&gt;
  
  
  Setting up &lt;code&gt;ltex-ls-plus&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ltex-ls-plus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Running &lt;code&gt;ltex-ls-plus&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ltex-cli-plus articles/20260213-setup-a-repository-to-store-blog-articles.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Checking links
&lt;/h3&gt;

&lt;p&gt;To check that the links in my articles are valid, I use &lt;code&gt;lychee&lt;/code&gt;, which is a fast and modern link checker.&lt;/p&gt;
&lt;h4&gt;
  
  
  Setting up &lt;code&gt;lychee&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;lychee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Running &lt;code&gt;lychee&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;lychee articles/20260213-setup-a-repository-to-store
5/5 ━━━━━━━━━━━━━━━━━━━━ Finished extracting links                                                                                                                                Issues found &lt;span class="k"&gt;in &lt;/span&gt;1 input. Find details below.

&lt;span class="o"&gt;[&lt;/span&gt;articles/20260213-setup-a-repository-to-store-blog-articles.md]:
   &lt;span class="o"&gt;[&lt;/span&gt;ERROR] file:///Users/david/dev/davidjulien/blog/articles/markdownlint-cli | Cannot find file: File not found. Check &lt;span class="k"&gt;if &lt;/span&gt;file exists and path is correct

🔍 5 Total &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;1s&lt;span class="o"&gt;)&lt;/span&gt; ✅ 4 OK 🚫 1 Error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One link isn't working. Without a line number, using &lt;code&gt;grep&lt;/code&gt; is necessary to find the error location.&lt;br&gt;
In my case, I permuted the link with the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="s2"&gt;(markdownlint"&lt;/span&gt;
34:[https://github.com/markdownlint/markdownlint]&lt;span class="o"&gt;(&lt;/span&gt;markdownlint&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automating local checks
&lt;/h2&gt;

&lt;p&gt;I set up four tools locally to check my articles: &lt;code&gt;markdownlint&lt;/code&gt;, &lt;code&gt;vale&lt;/code&gt;, &lt;code&gt;ltex-ls-plus&lt;/code&gt;, and &lt;code&gt;lychee&lt;/code&gt;.&lt;br&gt;
I want to run these tools before each commit to ensure that I don't commit any errors.&lt;/p&gt;
&lt;h3&gt;
  
  
  Git hooks
&lt;/h3&gt;

&lt;p&gt;To automate these checks, I use &lt;code&gt;git hooks&lt;/code&gt;, which are scripts that run at specific points in the Git workflow.&lt;br&gt;
The main problem is that the &lt;code&gt;.git&lt;/code&gt; directory isn't versioned, so I can't track changes in the hooks and share them with my collaborators.&lt;br&gt;
The solution is to store the hooks in a versioned directory (like &lt;code&gt;.githooks&lt;/code&gt;) and tell &lt;code&gt;git&lt;/code&gt; to use this directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; .githooks
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .githooks/pre-commit &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash
files=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;--diff-filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ACM | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'articles/.*\.md$'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;

if [ -z "&lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="sh"&gt;" ]; then
  exit 0
fi

echo "--- Markdown syntax ---"
markdownlint-cli2 &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="sh"&gt; || exit 1

echo "--- Check links ---"
lychee &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="sh"&gt; || exit 1

echo "--- Style ---"
vale &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="sh"&gt; || exit 1

echo "--- Grammar ---"
JAVA_OPTS="--enable-native-access=ALL-UNNAMED" ltex-cli-plus &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="sh"&gt; || exit 1

echo "Check terminated with success."
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .githooks/pre-commit

&lt;span class="nv"&gt;$ &lt;/span&gt;git config core.hooksPath .githooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;.githooks/pre-commit
&lt;span class="nt"&gt;---&lt;/span&gt; Markdown syntax &lt;span class="nt"&gt;---&lt;/span&gt;
markdownlint-cli2 v0.21.0 &lt;span class="o"&gt;(&lt;/span&gt;markdownlint v0.40.0&lt;span class="o"&gt;)&lt;/span&gt;
Finding: articles/20260213-setup-a-repository-to-store-blog-articles.md articles/&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.md
Linting: 1 file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
Summary: 0 error&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt; Check links &lt;span class="nt"&gt;---&lt;/span&gt;
6/6 ━━━━━━━━━━━━━━━━━━━━ Finished extracting links                                                                                                                                🔍 6 Total &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;1s&lt;span class="o"&gt;)&lt;/span&gt; ✅ 6 OK 🚫 0 Errors

&lt;span class="nt"&gt;---&lt;/span&gt; Style &lt;span class="nt"&gt;---&lt;/span&gt;
✔ 0 errors, 0 warnings and 0 suggestions &lt;span class="k"&gt;in &lt;/span&gt;1 file.
&lt;span class="nt"&gt;---&lt;/span&gt; Grammar &lt;span class="nt"&gt;---&lt;/span&gt;
Check terminated with success.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An alternative is &lt;a href="https://github.com/evilmartians/lefthook" rel="noopener noreferrer"&gt;&lt;code&gt;lefthook&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
It has more features (run checks in parallel, better output formatting, etc.) but it requires a dependency.&lt;/p&gt;
&lt;h2&gt;
  
  
  Neovim integration
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Linters output in the editor
&lt;/h3&gt;

&lt;p&gt;Running all checks thanks to a script is good, but it's even better to have these checks running in real-time while writing the article.&lt;br&gt;
It allows you to fix errors right away and avoid having a long list of errors at the end.&lt;/p&gt;

&lt;p&gt;This part depends on your editor.&lt;br&gt;
Personally, I'm using Neovim with the following configuration:&lt;/p&gt;

&lt;p&gt;In my &lt;code&gt;lsp&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ltex_plus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;filetypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"markdown"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ltex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ltex_plus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my linter configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"mfussenegger/nvim-lint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linters_by_ft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;markdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"vale"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"markdownlint-cli2"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lint_augroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_augroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;clear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_autocmd&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s2"&gt;"BufEnter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"BufWritePost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"InsertLeave"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lint_augroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;try_lint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can see directly all errors in my editor while writing the article.&lt;br&gt;
I can also navigate between errors with the following shortcuts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keymap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;leader&amp;gt;q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnostic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setloclist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Open diagnostics list'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;leader&amp;gt;ca"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code_action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"[C]ode [A]ction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"x"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Preview in the browser
&lt;/h3&gt;

&lt;p&gt;Writing Markdown is cool, but sometimes you need to check the rendering. For that, I install the &lt;code&gt;markdown-preview.nvim&lt;/code&gt; plugin, which allows seeing a live preview of my article in the browser while writing it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"iamcco/markdown-preview.nvim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"MarkdownPreviewToggle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MarkdownPreview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MarkdownPreviewStop"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;ft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"markdown"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cd app &amp;amp;&amp;amp; npm install"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I just need to run &lt;code&gt;:MarkdownPreview&lt;/code&gt; to see the preview in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous integration
&lt;/h2&gt;

&lt;p&gt;Since I'm the only one writing articles in my repository, I don't need to set up CI to enforce the checks. Git hooks ensure that I don't commit any changes that don't respect my quality standards. If you aren't alone, it's a good idea to set up CI to ensure that all articles go through the same checks before merging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Writing code and writing prose aren't that different. Both need fast feedback loops, consistent standards, and automation that catches what you miss. With this setup, every commit to my &lt;code&gt;blog&lt;/code&gt; repository goes through the same rigor I'd apply to commit a change in my code.&lt;/p&gt;

&lt;p&gt;The tools here — &lt;code&gt;markdownlint&lt;/code&gt;, &lt;code&gt;vale&lt;/code&gt;, &lt;code&gt;ltex-ls-plus&lt;/code&gt;, &lt;code&gt;lychee&lt;/code&gt; — took an afternoon to set up. Writing this article took more time!&lt;br&gt;
The feedback they give me every time I write is worth that investment many times over.&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>writing</category>
      <category>linting</category>
      <category>markdown</category>
    </item>
  </channel>
</rss>
