<?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: Marc Anton Dahmen</title>
    <description>The latest articles on DEV Community by Marc Anton Dahmen (@marcantondahmen).</description>
    <link>https://dev.to/marcantondahmen</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%2F2968428%2Fed73160c-08e6-45cf-8509-d8801732f813.png</url>
      <title>DEV Community: Marc Anton Dahmen</title>
      <link>https://dev.to/marcantondahmen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marcantondahmen"/>
    <language>en</language>
    <item>
      <title>Automad 2.0 Beta, the Next-Level CMS</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Mon, 29 Dec 2025 18:37:50 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/automad-20-beta-the-next-level-cms-1n39</link>
      <guid>https://dev.to/marcantondahmen/automad-20-beta-the-next-level-cms-1n39</guid>
      <description>&lt;p&gt;Today I’m excited to finally share something I’ve been working towards for a long time: the &lt;a href="https://automad.org/version-2" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; 2 beta is out.   &lt;/p&gt;

&lt;p&gt;If you’d rather experience it than read about it, you can try the &lt;a href="https://try.automad.org" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; right now and explore Automad 2 directly in your browser. A fresh demo site is created automatically, so you can jump straight into editing and get a feel for the new workflow within seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://automad.org/version-2" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; 2 is not a small iteration on version 1. It’s a rethink of what a modern, lightweight CMS can feel like — both for people editing content every day and for developers who care about clarity, performance, and long-term maintainability. The goal was simple: keep Automad fast and predictable, but make it far more powerful and pleasant to use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9g71da51f8szgzemju5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9g71da51f8szgzemju5.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing you’ll notice is the new &lt;em&gt;Standard Lite&lt;/em&gt; theme. It’s a fresh, elegant multipurpose theme that works out of the box for a wide range of projects, from personal sites to small business pages. It’s intentionally minimal, but flexible enough to grow with your content.&lt;/p&gt;

&lt;p&gt;Editing content has taken a big step forward. Reusable components let you group blocks once and reuse them across the entire site. &lt;strong&gt;Update a component in one place, and it updates everywhere.&lt;/strong&gt; This makes it much easier to maintain consistent layouts and shared content without copy-pasting yourself into trouble.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://automad.org/version-2" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; 2 also brings native &lt;strong&gt;multilanguage support&lt;/strong&gt;, making it straightforward to manage content in multiple languages without hacks or workarounds. Combined with a new &lt;strong&gt;draft and publish workflow&lt;/strong&gt;, you can preview changes safely before they go live, while &lt;em&gt;auto-saving&lt;/em&gt; ensures nothing gets lost while you edit.&lt;/p&gt;

&lt;p&gt;The editor itself has grown more capable and expressive. Code snippets now come with &lt;em&gt;syntax highlighting&lt;/em&gt;, and there are &lt;strong&gt;many new block types&lt;/strong&gt; available, including &lt;em&gt;collapsible sections&lt;/em&gt;, &lt;em&gt;callout boxes&lt;/em&gt;, and even &lt;em&gt;Math/TeX&lt;/em&gt; support for more technical content. Behind the scenes, &lt;strong&gt;page versioning&lt;/strong&gt; keeps track of changes and allows you to restore older versions of a page whenever you need to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw6b9oi11vpo4dgotz8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw6b9oi11vpo4dgotz8g.png" alt=" " width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the more practical side, Automad 2 finally adds SMTP email configuration for transactional emails and contact forms, making integrations more reliable and production-ready. All of this is wrapped in a &lt;em&gt;completely refreshed dashboard&lt;/em&gt; that feels calmer, clearer, and more focused on the task at hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is a beta release&lt;/strong&gt;, and the documentation is still a work in progress. Some rough edges are expected, and feedback at this stage is incredibly valuable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://try.automad.org" rel="noopener noreferrer"&gt;Try the live demo&lt;/a&gt;, explore the new editor, and see how &lt;a href="https://automad.org/version-2" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; 2 feels in practice. If you like where it’s going — or if something feels off — I’d love to hear from you. Automad has always been shaped by real-world use, and this next version is no exception.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>buildinpublic</category>
      <category>website</category>
    </item>
    <item>
      <title>Building Sortable Tree — A Lightweight Drag &amp; Drop Tree in Vanilla TypeScript</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Thu, 06 Nov 2025 17:00:00 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/building-sortable-tree-a-lightweight-drag-drop-tree-in-vanilla-typescript-f7l</link>
      <guid>https://dev.to/marcantondahmen/building-sortable-tree-a-lightweight-drag-drop-tree-in-vanilla-typescript-f7l</guid>
      <description>&lt;p&gt;When working on the &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; dashboard, I needed a collapsible and sortable navigation tree for the page browser sidebar — something that would feel like a small file explorer inside the browser. My goal was simple: use plain TypeScript, no frameworks, and rely only on native browser APIs.&lt;/p&gt;

&lt;p&gt;So I went looking for an existing library. There are countless tree components on &lt;code&gt;npm&lt;/code&gt; — but nearly all of them depend on React, Vue, or another frontend framework. I wanted something that works in any context: a small script I could drop into a project and wire up using plain events and DOM nodes.&lt;/p&gt;

&lt;p&gt;Nothing like that existed.&lt;br&gt;&lt;br&gt;
So I built it.    &lt;/p&gt;
&lt;h2&gt;
  
  
  From a Small Utility to a Full Library
&lt;/h2&gt;

&lt;p&gt;What started as a small experiment for the Automad dashboard eventually became a complete standalone library: &lt;a href="https://github.com/marcantondahmen/sortable-tree" rel="noopener noreferrer"&gt;sortable-tree&lt;/a&gt; — a lightweight, dependency-free (well, almost) TypeScript package that can create &lt;strong&gt;foldable&lt;/strong&gt; and &lt;strong&gt;sortable&lt;/strong&gt; tree structures with &lt;strong&gt;drag-and-drop&lt;/strong&gt; support.&lt;/p&gt;

&lt;p&gt;It’s designed for file explorers, navigation editors, or any interface where a nested structure needs to be reorganized interactively. No virtual DOMs. &lt;em&gt;No framework abstractions.&lt;/em&gt; Just events, DOM nodes, and TypeScript.&lt;/p&gt;
&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;For the Automad dashboard, I needed a tree that could do a few very specific things: foldable branches, sortable nodes via drag-and-drop, and a way to keep the state even if the page reloads. It also had to be fully stylable so it could match the dashboard’s clean look — and all of this had to work without pulling in a framework. &lt;code&gt;sortable-tree&lt;/code&gt; was built exactly to meet these needs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Written in plain TypeScript, framework-agnostic&lt;/li&gt;
&lt;li&gt;Drag and drop sorting&lt;/li&gt;
&lt;li&gt;Foldable branches&lt;/li&gt;
&lt;li&gt;Optional confirmation when moving nodes around&lt;/li&gt;
&lt;li&gt;Persisting state across page reloads&lt;/li&gt;
&lt;li&gt;Fully stylable — customize icons, node templates, and CSS&lt;/li&gt;
&lt;li&gt;(Almost) no dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Basic Example
&lt;/h2&gt;

&lt;p&gt;Here’s a minimal setup that renders a simple tree that persits state across page reloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SortableTree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SortableTreeNodeData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sortable-tree&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sortable-tree/dist/sortable-tree.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// basic styles&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SortableTreeNodeData&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;nodes&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;nodes&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subpage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;nodes&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="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SortableTree&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#tree&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;stateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-tree&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initCollapseLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;renderLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;span&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; 
    &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;movedNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;srcParentNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;targetParentNode&lt;/span&gt; 
  &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;movedNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library takes care of all drag-and-drop logic, nesting, and fold state management internally. It provides simple callback options like &lt;code&gt;onClick&lt;/code&gt; and &lt;code&gt;onChange&lt;/code&gt; to handle user interactions directly in your code. You can, for example, listen to when a node is moved or clicked and react accordingly — or even define optional &lt;em&gt;confirmation logic&lt;/em&gt; before changes are applied. If you’d like to see it in action, check out the &lt;a href="https://marcantondahmen.github.io/sortable-tree/" rel="noopener noreferrer"&gt;live demo and examples&lt;/a&gt; — you can play around with drag-and-drop, folding, and custom styling right in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating in Real Projects
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://automad.org/" rel="noopener noreferrer"&gt;Automad&lt;/a&gt;, this library powers the page browser sidebar. Pages can be folded and reordered with drag-and-drop, just like files in a file explorer.&lt;/p&gt;

&lt;p&gt;Because Automad’s dashboard is written entirely in plain TypeScript — &lt;em&gt;no frameworks&lt;/em&gt; — it’s a perfect example of how simple browser APIs can scale. The code is easy to read, inspect, and extend.&lt;/p&gt;

&lt;p&gt;This simplicity is intentional. I wrote more about why Automad doesn’t rely on frameworks in &lt;a href="https://blog.marcdahmen.de/articles/why-automad-no-longer-uses-any-frontend-or-backend-framework" rel="noopener noreferrer"&gt;another blog post&lt;/a&gt; — but in short: the less you depend on abstractions, the longer your software will live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;You can install the package directly from npm:&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;sortable-tree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then import it in your TypeScript or JavaScript project and render your first tree. Documentation, demos, and additional examples are available on the &lt;a href="https://marcantondahmen.github.io/sortable-tree/" rel="noopener noreferrer"&gt;docs and demo site&lt;/a&gt;. The full source code is hosted on &lt;a href="https://github.com/marcantondahmen/sortable-tree" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;What started as a missing piece in the Automad dashboard turned into a reusable, framework-free library for others to build on. From my perspective, &lt;em&gt;sortable-tree&lt;/em&gt; shows that even complex UI patterns like nested drag-and-drop don’t need heavy dependencies — just a clear understanding of how the browser already works and what your project really needs.&lt;/p&gt;

&lt;p&gt;It’s fully customizable, keeps state across reloads, and works anywhere TypeScript or JavaScript runs — a small tool that perfectly fits the kind of straightforward, practical approach Automad embraces.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>development</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why I Prefer to Work Alone on My Personal Projects</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Sat, 27 Sep 2025 18:07:12 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/why-i-prefer-to-work-alone-on-my-personal-projects-46md</link>
      <guid>https://dev.to/marcantondahmen/why-i-prefer-to-work-alone-on-my-personal-projects-46md</guid>
      <description>&lt;p&gt;Open source often comes with an expectation: projects are best built together. And in many cases, that’s true. In my professional life, I collaborate constantly — pair programming, code reviews, team planning. I know firsthand the value of working with others, and I enjoy it.&lt;/p&gt;

&lt;p&gt;But when it comes to my personal projects, I prefer a different approach. I like to work alone.&lt;/p&gt;

&lt;p&gt;Take &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt;, for example. For me, it isn’t just a codebase — it’s the result of years of late-night experiments, side paths, and “what if I try this?” ideas. It’s my creative playground. The moment a pull request appears, that dynamic shifts. Even the smallest change — say, a one-line fix — suddenly gets logged as a contribution next to years of effort. Bigger contributions come with their own challenges: they require discussions, planning, compromises, and agreements. And before I know it, my personal project starts to feel like my day job.&lt;/p&gt;

&lt;p&gt;There’s also the personal side of it. I’ve invested so much of myself into these projects that collaborating with complete strangers can feel strangely uncomfortable. It’s not about doubting someone’s abilities; it’s about maintaining a space where I can follow my own instincts without pressure, where I can make mistakes or abandon ideas without worrying about stepping on someone else’s work.&lt;/p&gt;

&lt;p&gt;And then there’s the practical side: contributions take time to manage. Even the best pull requests rarely merge themselves. They need to be reviewed, tested, and adjusted to fit the project’s standards. Every one of those steps takes time and energy — time I’d rather spend actually creating, refining, or exploring new directions.&lt;/p&gt;

&lt;p&gt;So yes, I choose to work alone. But here’s the important part: I still open-source my projects. Because open source isn’t only about collaboration — it’s also about sharing. A project doesn’t need a hundred contributors to bring value to the world. Even when it’s built by just one person, it can still help others solve problems, spark ideas, or teach something new.&lt;/p&gt;

&lt;p&gt;That’s the balance I’ve found works best for me. By keeping my creative process personal, I protect the freedom and flow that make these projects enjoyable. And by open-sourcing the results, I make sure that freedom can still create value for others.&lt;/p&gt;

&lt;p&gt;In the end, I don’t reject collaboration — I just draw a line between where it belongs. At work, collaboration is essential. In my personal projects, solitude keeps the spark alive. And thanks to open source, those sparks don’t have to stay mine alone.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>softwaredevelopment</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Automatic Cookie Consent Banners in Automad</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Thu, 08 May 2025 07:11:21 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/automatic-cookie-consent-banners-in-automad-4nim</link>
      <guid>https://dev.to/marcantondahmen/automatic-cookie-consent-banners-in-automad-4nim</guid>
      <description>&lt;p&gt;Cookie consent banners are, in my opinion, one of the worst things to have happened to the modern web. They clutter interfaces, confuse users, and ultimately serve as a band-aid for a problem that should be solved at the browser level — not by every single website reinventing the wheel in JavaScript.&lt;/p&gt;

&lt;p&gt;That said, like it or not, cookie consent is a legal requirement in many parts of the world, especially in the EU. And since many Automad users need to comply, I’ve added built-in support for cookie consent banners in the latest alpha version of the &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; CMS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cookie Consent That Just Works
&lt;/h2&gt;

&lt;p&gt;The new alpha version of &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; introduces a built-in cookie consent system that takes care of everything automatically. You don’t need to install a plugin, write custom JavaScript, or hack together your own solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatically Displayed When Needed
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; now detects when a page has embedded third-party content that potentially sets cookies — like an embedded YouTube video or Soundcloud song — and displays a consent banner only when it’s necessary. If your page doesn’t include anything privacy-sensitive, no banner is shown at all. Simple as that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle Manually Added Third-Party Scripts
&lt;/h3&gt;

&lt;p&gt;For more advanced use cases, Automad includes the new web component. You can wrap any JavaScript block (e.g., social media widgets, third-party analytics, or anything cookie-related) in this component to ensure it’s only loaded after user consent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;am-consent&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"script"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    console.log('Hello you!');
&lt;span class="nt"&gt;&amp;lt;/am-consent&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also wrap remote JavaScript files in the consent logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;am-consent&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"script"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://domain.com/script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/am-consent&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you full control while maintaining compliance. &lt;/p&gt;

&lt;h2&gt;
  
  
  Fully Customizable Look and Feel
&lt;/h2&gt;

&lt;p&gt;No one wants a generic banner that looks like it was slapped on at the last minute. Automad lets you customize the consent banner entirely via the dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the banner text and button labels&lt;/li&gt;
&lt;li&gt;Adjust background and text colors&lt;/li&gt;
&lt;li&gt;Change the colors of the embedded content placeholders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, the banner and placeholders feel like part of your design, not a foreign element. All cookie consent customization settings can be found either in &lt;em&gt;General Data and Files&lt;/em&gt; &amp;gt; &lt;em&gt;Customizations&lt;/em&gt; section or under the &lt;em&gt;Customizations&lt;/em&gt; tab in the page settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Small Step Toward a Smoother Web
&lt;/h2&gt;

&lt;p&gt;While cookie banners aren’t going away anytime soon, Automad now helps you deal with them in the least painful way possible. They appear only when needed, they don’t break your design, and you remain in control.&lt;/p&gt;

&lt;p&gt;It’s a compromise between legal obligation and design minimalism — and another step toward making Automad an even more complete and effortless CMS for modern websites.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>news</category>
      <category>webcomponents</category>
      <category>website</category>
    </item>
    <item>
      <title>The Inflation of "Security Researchers" and Its Consequences for Open Source</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Sat, 05 Apr 2025 07:06:05 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/the-inflation-of-security-researchers-and-its-consequences-for-open-source-1feg</link>
      <guid>https://dev.to/marcantondahmen/the-inflation-of-security-researchers-and-its-consequences-for-open-source-1feg</guid>
      <description>&lt;p&gt;As an open-source maintainer, I deeply appreciate the importance of cybersecurity. Security is a shared responsibility — both for users who rely on software to be secure and for developers who build and maintain open-source projects. Responsible vulnerability reporting strengthens the ecosystem, helping us all build better, safer software. However, in recent years, the term "security researcher" has been stretched to the point where it is becoming counterproductive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with False Positive CVEs
&lt;/h2&gt;

&lt;p&gt;Security should always be taken seriously. When genuine vulnerabilities are discovered and responsibly reported, it leads to stronger software. However, the rise of false positive &lt;a href="https://de.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures" rel="noopener noreferrer"&gt;CVEs&lt;/a&gt; (Common Vulnerabilities and Exposures) is eroding trust in the entire system. When reports are filled with exaggerated, misclassified, or outright incorrect vulnerabilities, security research turns from a net positive into a liability.&lt;/p&gt;

&lt;p&gt;The more false positives flood the system, the harder it becomes to separate real threats from noise. If every minor misconfiguration, theoretical issue, or misunderstanding of an application’s context results in a CVE, security teams — and open-source maintainers like me — will struggle to prioritize what truly matters. This leads to a dangerous situation: if everything is a "critical" vulnerability, nothing is.&lt;/p&gt;

&lt;p&gt;A second, equally concerning risk is that the increasing burden of handling false or exaggerated vulnerability reports could discourage open-source maintainers from continuing their work. Many of us develop and maintain open-source projects in our free time, often without compensation. When dealing with bad-faith reports, inflated CVE claims, and a lack of appreciation becomes overwhelming, it’s not hard to imagine that some maintainers might choose to step away from their projects. If that happens on a larger scale, the entire open-source ecosystem could suffer — not because of real security threats, but because the noise is drowning out legitimate concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Domain Before Reporting
&lt;/h2&gt;

&lt;p&gt;One key issue in today's security research landscape is the lack of context in vulnerability reporting. Not all software is the same, and not all theoretical vulnerabilities are real-world threats.&lt;/p&gt;

&lt;p&gt;A security vulnerability in a desktop application may not be relevant in a cloud-based system. A theoretical attack vector that requires unrealistic conditions — like admin access already being compromised — may not be a true risk at all. Yet, reports often do not consider these nuances. As a maintainer of &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt;, a content management system, I have seen reports that claim a severe risk without understanding how the system actually works in a real deployment.&lt;/p&gt;

&lt;p&gt;Without proper domain knowledge, security researchers risk creating unnecessary panic over non-issues. Worse, this can lead to maintainers being overwhelmed with bad reports, which in turn slows down the response to actual security concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real-World Example
&lt;/h2&gt;

&lt;p&gt;A perfect example of this issue happened last year when I received multiple CVE notifications about a supposed cross-site scripting (XSS) vulnerability in &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt;. The reports claimed that a non-sanitized input field could be exploited to inject JavaScript. However, these reports completely misunderstood the nature of the project. Automad is designed as a single-user content management system, meaning there are no user sessions to steal, and the only person with access is the site owner — who already has full control over the server. While it is possible to add other trusted collaborators, Automad does not include role-based access management or a permission system — this is intentional. As a minimalistic CMS, it is designed for simplicity rather than complex user management. This fact alone eliminates any meaningful attack vector for XSS.&lt;/p&gt;

&lt;p&gt;Even more ironic is that the actual purpose of this field is to modify the template, which includes the ability to add JavaScript. It is an essential feature, not a vulnerability. Filtering out JavaScript in this context would make Automad less functional for its intended users. Yet, these reports reflect an extremely low intellectual quality, reducing the complexity of web security to an oversimplified "JavaScript = bad" narrative. This kind of thinking ignores the fact that JavaScript is a fundamental part of modern web development and that &lt;strong&gt;context matters&lt;/strong&gt;. Not every instance of JavaScript is a security risk, and not every field that allows JavaScript input is a vulnerability. This level of reasoning results in false reports that waste time and distract from real security threats.&lt;/p&gt;

&lt;p&gt;Following the logic of these reports, one could argue that all static websites on all servers are, by definition, a security threat — since they allow JavaScript to be embedded freely. But that would be an absurd claim. An admin is an admin, and with those privileges comes responsibility. If we start labeling every instance of an admin performing admin tasks as a security flaw, we will render security research meaningless. Despite this, the CVEs were still registered, adding unnecessary noise to security databases and further proving how poorly contextualized vulnerability reports can lead to misleading conclusions. The situation escalated to the point where even &lt;em&gt;INCIBE&lt;/em&gt;, Spain's national cybersecurity institute, reached out to ask me to react — further illustrating how these flawed reports can create unnecessary bureaucracy and pressure on open-source maintainers. It was clear that nobody involved had actually taken a proper look at the issue or was simply unable to understand the affected code.&lt;/p&gt;

&lt;p&gt;Fortunately, the at least the GitHub Security Advisory team reviewed the reported CVEs listed there and, after assessing the claims, decided to withdraw most of them (&lt;a href="https://github.com/advisories/GHSA-g8h2-j9pm-4xx2" rel="noopener noreferrer"&gt;CVE-2024-40111&lt;/a&gt;, &lt;a href="https://github.com/advisories/GHSA-fpph-mqc8-h6q5" rel="noopener noreferrer"&gt;CVE-2023-7036&lt;/a&gt; and  &lt;a href="https://github.com/advisories/GHSA-7j9h-ch38-474r" rel="noopener noreferrer"&gt;CVE-2023-7035&lt;/a&gt;). This demonstrates that not all security platforms blindly accept reports and that careful review processes can help mitigate the spread of misleading vulnerability claims. However, the fact that these CVEs were published in the first place still highlights a major problem: maintainers must invest time and effort into defending their projects against false positives rather than focusing on actual development and security improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data: False Positives on the Rise
&lt;/h2&gt;

&lt;p&gt;Studies and industry reports indicate that a significant percentage of reported CVEs are now false positives. This is a troubling trend because, instead of strengthening security, it weakens it. When organizations and maintainers are forced to sift through countless invalid reports, real threats can be missed.&lt;/p&gt;

&lt;p&gt;For example, a study by JFrog found that &lt;em&gt;78% of reported CVEs in popular DockerHub images were not actually exploitable&lt;/em&gt; [&lt;a href="https://jfrog.com/blog/turns-out-78-of-reported-cves-on-top-dockerhub-images-are-not-really-exploitable/" rel="noopener noreferrer"&gt;source&lt;/a&gt;]. Similarly, discussions in the open-source community have highlighted &lt;em&gt;how many CVEs are assigned without proper validation&lt;/em&gt;, leading to unnecessary panic and wasted resources [&lt;a href="https://lwn.net/Articles/944209/" rel="noopener noreferrer"&gt;LWN article&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;A well-documented case that illustrates this problem is CVE-2020-19909, which was recently reassigned as a "critical" vulnerability in &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;Curl&lt;/a&gt; — despite being a decades-old, non-exploitable bug. This case exposes the systemic flaws in how CVEs are assigned and scored [&lt;a href="https://daniel.haxx.se/blog/2023/08/26/cve-2020-19909-is-everything-that-is-wrong-with-cves/" rel="noopener noreferrer"&gt;Daniel Stenberg’s blog&lt;/a&gt;][&lt;a href="https://news.ycombinator.com/item?id=37608110" rel="noopener noreferrer"&gt;Hacker News discussion&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;This is not just an open-source problem — it affects the entire cybersecurity field. If security research continues to focus on quantity over quality, the long-term effect will be a loss of credibility. Users, developers, and security teams will start ignoring vulnerability reports altogether, assuming they are just more noise. This is the exact opposite of what cybersecurity research should achieve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Way Forward
&lt;/h2&gt;

&lt;p&gt;For security research to be effective, we need to emphasize responsible disclosure and domain expertise. Researchers should take the time to understand a system before reporting vulnerabilities. The industry needs better guidelines to filter out false positives before they are assigned CVEs. Open-source maintainers, in turn, need to be given the space to address real issues rather than constantly responding to invalid reports.&lt;/p&gt;

&lt;p&gt;Cybersecurity is too important to be diluted by low-effort, context-free reports. If we want to improve security, we must ensure that vulnerability reports are meaningful, accurate, and relevant. Otherwise, we risk making the internet a less secure place — despite the best of intentions.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>programming</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Why Automad No Longer Uses Any Frontend or Backend Framework</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Fri, 28 Mar 2025 11:00:00 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/why-automad-no-longer-uses-any-frontend-or-backend-framework-3pod</link>
      <guid>https://dev.to/marcantondahmen/why-automad-no-longer-uses-any-frontend-or-backend-framework-3pod</guid>
      <description>&lt;p&gt;Long-term open-source projects, such as &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt;, require stability and independence from third-party libraries. Relying on external frameworks introduces risks that can impact maintainability, long-term support, and overall project longevity.&lt;/p&gt;

&lt;p&gt;Removing dependencies on &lt;em&gt;jQuery&lt;/em&gt; and &lt;em&gt;UIkit&lt;/em&gt; in preparation for Automad 2 was a painful process that ultimately led to an entire re-write. This transition required significant effort to replace legacy implementations with modern, native solutions while ensuring feature parity and long-term stability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Appeal of Frameworks
&lt;/h2&gt;

&lt;p&gt;In my day job as a software engineer, I work extensively with &lt;em&gt;React&lt;/em&gt; and &lt;em&gt;Svelte&lt;/em&gt;, along with their respective metaframeworks, &lt;em&gt;Next.js&lt;/em&gt; and &lt;em&gt;SvelteKit&lt;/em&gt;. These tools provide significant advantages in modern development workflows, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for teams with varying skill levels&lt;/li&gt;
&lt;li&gt;Rapid development&lt;/li&gt;
&lt;li&gt;Easy onboarding&lt;/li&gt;
&lt;li&gt;Common industry knowledge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, these benefits come with a hidden cost: dependency on the decisions of framework authors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Costs of Framework Dependency
&lt;/h2&gt;

&lt;p&gt;When adopting a frontend or backend framework, you are not just using a tool — you are also agreeing to follow the evolution of that tool. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Breaking changes&lt;/li&gt;
&lt;li&gt;Shifting paradigms&lt;/li&gt;
&lt;li&gt;Ecosystem lock-in&lt;/li&gt;
&lt;li&gt;Uncertain longevity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a project like Automad, which aims for long-term maintainability and minimal dependencies, these issues present a real challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Case for Native Solutions
&lt;/h2&gt;

&lt;p&gt;Instead of relying on third-party frameworks, Automad embraces native web technologies where possible. Native solutions, such as Web Components, provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable APIs&lt;/li&gt;
&lt;li&gt;No external dependencies&lt;/li&gt;
&lt;li&gt;Built-in browser support&lt;/li&gt;
&lt;li&gt;Full component lifecycle management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite these advantages, Web Components have often been met with skepticism, primarily due to negative perceptions perpetuated by framework maintainers. Many articles critiquing Web Components come from authors with vested interests in framework adoption.   &lt;/p&gt;

&lt;p&gt;That being said, using smaller libraries and packages is not a problem as long as they can be easily isolated and replaced. Unlike monolithic frameworks, modular libraries provide flexibility without locking a project into a particular ecosystem or paradigm.&lt;/p&gt;

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

&lt;p&gt;Automad's decision to move away from frontend and backend frameworks is driven by a long-term vision of stability, maintainability, and independence. While frameworks provide immediate benefits, they also introduce long-term risks that are incompatible with the goals of an open-source CMS that seeks to remain lightweight, flexible, and future-proof. By leveraging native solutions, &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; ensures a stable foundation that is not subject to the shifting priorities of third-party framework maintainers.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>webcomponents</category>
      <category>programming</category>
    </item>
    <item>
      <title>Setting Up Neovim for Automad Theme Development</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Wed, 26 Mar 2025 11:30:00 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/setting-up-neovim-for-automad-theme-development-3852</link>
      <guid>https://dev.to/marcantondahmen/setting-up-neovim-for-automad-theme-development-3852</guid>
      <description>&lt;p&gt;As the developer of the &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; flat-file content management system, I wanted to make working with Automad themes in &lt;a href="https://neovim.io" rel="noopener noreferrer"&gt;Neovim&lt;/a&gt; as smooth as possible. Automad has its own templating syntax, so I created &lt;a href="https://github.com/automadcms/tree-sitter-automad" rel="noopener noreferrer"&gt;tree-sitter-automad&lt;/a&gt; to provide proper syntax highlighting. Since it’s not yet an official parser, you need to manually register it in Neovim. To speed up template writing, you can also add custom snippets using &lt;a href="https://github.com/L3MON4D3/LuaSnip" rel="noopener noreferrer"&gt;LuaSnip&lt;/a&gt;. Here's how to set everything up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Syntax Highlighting with Tree-sitter
&lt;/h2&gt;

&lt;p&gt;By default, Neovim doesn't recognize &lt;a href="https://github.com/automadcms/tree-sitter-automad" rel="noopener noreferrer"&gt;tree-sitter-automad&lt;/a&gt; since it isn't an official parser yet. You can manually register it by modifying your Neovim config. Open &lt;code&gt;~/.config/nvim/init.lua&lt;/code&gt; (or &lt;code&gt;~/.config/nvim/lua/plugins/treesitter.lua&lt;/code&gt; if you separate plugins) and add:&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="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;parser_config&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="s1"&gt;'nvim-treesitter.parsers'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;get_parser_configs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;parser_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;automad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;install_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://github.com/automadcms/tree-sitter-automad'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'src/parser.c'&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="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nvim-treesitter.configs'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ensure_installed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"lua"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"automad"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file and restart Neovim. Now Neovim will recognize Automad templates and provide syntax-aware highlighting.   &lt;/p&gt;

&lt;p&gt;Apart from the parser, Tree-sitter also needs query files to define syntax highlighting rules. Again, since Automad isn't an official parser yet, these queries aren't included by default, so you need to copy them manually.   &lt;/p&gt;

&lt;p&gt;Clone the tree-sitter-automad repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/automadcms/tree-sitter-automad.git ~/tree-sitter-automad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create the directories where Neovim looks for Tree-sitter queries:&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; ~/.config/nvim/after/queries/php
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/nvim/after/queries/automad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the query files from the repository into these directories:&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;cp&lt;/span&gt; ~/tree-sitter-automad/queries/&lt;span class="k"&gt;*&lt;/span&gt; ~/.config/nvim/after/queries/php/
&lt;span class="nb"&gt;cp&lt;/span&gt; ~/tree-sitter-automad/queries/&lt;span class="k"&gt;*&lt;/span&gt; ~/.config/nvim/after/queries/automad/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that's done, restart Neovim and check if the parser is working by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:checkhealth nvim-treesitter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is set up correctly, automad should appear in the list of installed parsers. You can also take a look at my personal &lt;a href="https://github.com/marcantondahmen/nvim-config/blob/e205cdc08ac195d0f2f478339d5251ce5aea75a7/lua/marcantondahmen/plugins/treesitter.lua#L19" rel="noopener noreferrer"&gt;Neovim config&lt;/a&gt; repository to see how I have everything set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Automad Snippets with LuaSnip
&lt;/h2&gt;

&lt;p&gt;I personally use &lt;a href="https://github.com/L3MON4D3/LuaSnip" rel="noopener noreferrer"&gt;LuaSnip&lt;/a&gt; to manage my Neovim snipptes. To make writing Automad templates faster, I recommend that you define some custom Automad snippets directly inside your &lt;em&gt;LuaSnip&lt;/em&gt; config. If you want to see how I integrate it into my setup, you can check out my personal &lt;a href="https://github.com/marcantondahmen/nvim-config/blob/e205cdc08ac195d0f2f478339d5251ce5aea75a7/lua/marcantondahmen/plugins/luasnip.lua#L27" rel="noopener noreferrer"&gt;Neovim config&lt;/a&gt; repository.&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;local &lt;/span&gt;luasnip &lt;span class="o"&gt;=&lt;/span&gt; require&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'luasnip'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;local &lt;/span&gt;s &lt;span class="o"&gt;=&lt;/span&gt; luasnip.snippet
&lt;span class="nb"&gt;local &lt;/span&gt;t &lt;span class="o"&gt;=&lt;/span&gt; luasnip.text_node
&lt;span class="nb"&gt;local &lt;/span&gt;i &lt;span class="o"&gt;=&lt;/span&gt; luasnip.insert_node

luasnip.add_snippets&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'php'&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Automad Statement'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;, 
    &lt;span class="o"&gt;{&lt;/span&gt; t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Automad Variable'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;, 
    &lt;span class="o"&gt;{&lt;/span&gt; t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'@{ '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' }'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'#'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Automad Comment'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;, 
    &lt;span class="o"&gt;{&lt;/span&gt; t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;# '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' #&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@pagelist'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'foreach in pagelist'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt; t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ foreach in pagelist @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'    '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@pagelistelse'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'foreach in pagelist else'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;, 
    &lt;span class="o"&gt;{&lt;/span&gt;
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ foreach in pagelist @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'    '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ else @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'   '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@if'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'if ...'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt; t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ if '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'   '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@ifelse'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'if ... else ...'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;, 
    &lt;span class="o"&gt;{&lt;/span&gt;
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ if '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'  '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ else @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'   '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@with'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'with ...'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt; t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ with '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, i&lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;, t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@withelse'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'with ... else ...'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;, 
    &lt;span class="o"&gt;{&lt;/span&gt;
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ with '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'  '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ else @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'   '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@for'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'for ... to ...'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;, 
    &lt;span class="o"&gt;{&lt;/span&gt;
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ for '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' to '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'  '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;,
      i&lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;,
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
  s&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt; trig &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'@snippet'&lt;/span&gt;, name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'snippet ...'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt; 
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;@ snippet '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, 
      i&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;, 
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;' @&amp;gt;'&lt;/span&gt;, &lt;span class="s1"&gt;'  '&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;, 
      i&lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;, 
      t&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;, &lt;span class="s1"&gt;'&amp;lt;@ end @&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;,
&lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can type Automad syntax triggers like &lt;code&gt;@if&lt;/code&gt;, &lt;code&gt;@for&lt;/code&gt;, or &lt;code&gt;&amp;lt;@&lt;/code&gt;, then press Tab (or your configured expansion key) to insert full syntax structures automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;With Tree-sitter and LuaSnip set up, developing &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; themes in Neovim becomes much smoother. You now have syntax highlighting and custom snippets to speed up your workflow.   &lt;/p&gt;

&lt;p&gt;If you run into any issues, feel free to submit an issue on the &lt;a href="https://github.com/automadcms/tree-sitter-automad" rel="noopener noreferrer"&gt;tree-sitter-automad&lt;/a&gt; repository. You can also check out my personal &lt;a href="https://github.com/marcantondahmen/nvim-config" rel="noopener noreferrer"&gt;Neovim config&lt;/a&gt; for more setup inspiration. Happy coding!&lt;/p&gt;

</description>
      <category>neovim</category>
      <category>php</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Create a Multilingual Website Using Automad 2</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Sun, 23 Mar 2025 15:45:34 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/how-to-create-a-multilingual-website-using-automad-2-hhl</link>
      <guid>https://dev.to/marcantondahmen/how-to-create-a-multilingual-website-using-automad-2-hhl</guid>
      <description>&lt;p&gt;Creating a multilingual website can significantly expand your reach and user engagement by catering to a global audience. &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; 2, a lightweight flat-file CMS that prides itself on simplicity and flexibility, makes it easy to create and manage multilingual websites.&lt;/p&gt;

&lt;p&gt;In this tutorial, I'll walk you through the steps to build a multilingual website using Automad 2. You'll learn how to set up language-specific landing pages, configure slugs, and enable language routing. Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Site Structure with Language-Specific Landing Pages
&lt;/h2&gt;

&lt;p&gt;The first step in creating a multilingual website is to organize your site structure. In Automad 2, you want to create a structure where each language has its own landing page directly under the homepage. This will make it easier to manage the content for each language and keep things organized.    &lt;/p&gt;

&lt;p&gt;Here’s how to do it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log into your Automad 2 admin panel.&lt;/li&gt;
&lt;li&gt;Go to the Pages section and add a new page for each language you want to support. For instance, you can create pages called “English,” “Français,” “Deutsch,” etc. These will serve as the landing pages for each language.&lt;/li&gt;
&lt;li&gt;Give each page any title you like at this stage — since you'll be changing the slugs in the next step, the title here doesn’t affect navigation or URLs. You can focus on whatever naming convention works best for your workflow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Important Note: The order of the pages matters! The first page at the top of the sidebar will serve as the default language for your site. If a visitor accesses your website with an unsupported locale, Automad will use this default language. Make sure to place your preferred default language page at the top of the list.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve created the pages, you should have a homepage that points to the different language-specific landing pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Change the Slug for Each Language Page
&lt;/h2&gt;

&lt;p&gt;By default, Automad will automatically generate slugs based on the titles you gave your language-specific landing pages. However, we need to change the slugs to the corresponding language codes (like &lt;code&gt;en&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, or &lt;code&gt;de&lt;/code&gt;) for proper routing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69x37l4h6x7cnxtr24de.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69x37l4h6x7cnxtr24de.png" alt="Automad CMS page settings" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s how to change the slugs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the settings of each language-specific landing page.&lt;/li&gt;
&lt;li&gt;In the Directory Name field (this field represents the page’s slug), change the automatically generated slug to the matching language code. For example:&lt;/li&gt;
&lt;li&gt;Make sure each landing page has its unique language code as the slug.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By setting these language codes as slugs, you’re telling Automad that these pages represent different languages, which is essential for proper language routing later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Enable Language Routing in the System Settings
&lt;/h2&gt;

&lt;p&gt;Now that you’ve created your language-specific landing pages and configured the slugs, it’s time to enable language routing. This ensures that Automad will handle the routing for your multilingual content based on the language code in the URL.&lt;/p&gt;

&lt;p&gt;Here’s how to enable language routing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to System Settings in the admin panel.&lt;/li&gt;
&lt;li&gt;Look for the Internationalization section.&lt;/li&gt;
&lt;li&gt;Find the Language Routing setting and enable it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Enabling this feature ensures that when someone visits your site with a specific language code in the URL (like &lt;code&gt;www.yoursite.com/en&lt;/code&gt;), Automad will automatically serve the content for that language. For example, if someone visits &lt;code&gt;www.yoursite.com/fr&lt;/code&gt;, they’ll be redirected to the French language version of the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Translate Your Content
&lt;/h2&gt;

&lt;p&gt;At this point, your multilingual site structure is set up, but it’s still empty. Now you’ll need to add translated content to each language-specific landing page. To do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to each language-specific page in the admin panel.&lt;/li&gt;
&lt;li&gt;Add the appropriate content for each language.&lt;/li&gt;
&lt;li&gt;You can use Automad’s simple block editor to add text, images, and other media that are relevant to each language.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repeat this for all the pages in your site, making sure each language version has its own unique content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Test Your Multilingual Website
&lt;/h2&gt;

&lt;p&gt;Once you’ve added all your content and completed the setup, it’s time to test your multilingual website. Make sure to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether each language-specific page loads correctly when visiting its respective URL (e.g., &lt;code&gt;/en&lt;/code&gt;, &lt;code&gt;/fr&lt;/code&gt;, &lt;code&gt;/de&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;That content is displayed correctly in each language and there are no broken links.&lt;/li&gt;
&lt;li&gt;Navigation and links between languages are working as expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus: Add links to Other Languages in the Footer
&lt;/h2&gt;

&lt;p&gt;Now, your multilingual site is ready for visitors. But what if you want to give those visitors the chance to change to another language? With language routing enabled, pages in other languages than the currently active one are not visible in the site navigation and can’t be found in the site search.    &lt;/p&gt;

&lt;p&gt;In order to allow visitors to change to another language, we can simply create page for another language that is alias inside the active language. With the three languages used in this tutorial, we can create the page &lt;code&gt;/en/german&lt;/code&gt; and forward it to the German landing page by changing the URL field to &lt;code&gt;/de&lt;/code&gt; in the page settings. Finally we can check the "Show Page in Footer" checkbox to show the link in the footer. This procedure we can also repeat for the French version as well as all remaining combinations inside the other two languages.&lt;/p&gt;

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

&lt;p&gt;Setting up a multilingual website with &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; 2 is simple and straightforward. By creating language-specific landing pages, modifying slugs to match language codes, and enabling language routing, you can effectively manage and deliver content in multiple languages. With Automad’s clean and intuitive interface, it’s easy to expand your reach to a global audience without overwhelming complexity.    &lt;/p&gt;

&lt;p&gt;Happy writing, and enjoy building your multilingual website with Automad 2!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>opensource</category>
      <category>cms</category>
    </item>
    <item>
      <title>Automad CMS 2.0 Alpha</title>
      <dc:creator>Marc Anton Dahmen</dc:creator>
      <pubDate>Sun, 23 Mar 2025 15:11:54 +0000</pubDate>
      <link>https://dev.to/marcantondahmen/automad-20-alpha-2lkl</link>
      <guid>https://dev.to/marcantondahmen/automad-20-alpha-2lkl</guid>
      <description>&lt;p&gt;After a long development process, the stable release of the &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad CMS&lt;/a&gt; 2, is getting closer. While many aspects of the system have evolved, the core vision remains the same — delivering a fast, flexible, and file-based content management system with a powerful templating engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New?
&lt;/h2&gt;

&lt;p&gt;With Automad 2 now in &lt;a href="https://automad.org/version-2" rel="noopener noreferrer"&gt;alpha&lt;/a&gt;, the list of improvements is extensive. A finalized documentation and release notes will provide full details soon, but here are already some of the most significant updates:&lt;/p&gt;

&lt;h3&gt;
  
  
  Modernized Frontend
&lt;/h3&gt;

&lt;p&gt;One of the biggest changes is the complete removal of UIkit and jQuery as dependencies. Instead, the frontend is now built entirely with modern web components and TypeScript, making the dashboard lighter, faster, and more maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhanced Block Editor
&lt;/h3&gt;

&lt;p&gt;The block editor has been significantly improved for a more intuitive editing experience, especially when working with nested layouts. Nested sections no longer require modal windows, and drag-and-drop functionality between sections is now fully supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native Multilingual Support
&lt;/h3&gt;

&lt;p&gt;Automad now includes built-in &lt;a href="https://blog.marcdahmen.de/articles/how-to-create-a-multilingual-website-using-automad-2" rel="noopener noreferrer"&gt;multilingual&lt;/a&gt; support. Visitors can be routed to different language versions based on their locale settings, and editors can define language-specific routes (e.g., &lt;code&gt;/de&lt;/code&gt; or &lt;code&gt;/en&lt;/code&gt;). If a visitor's locale doesn’t match any of the available options, the top-listed language is used as the default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Editing Within Automad
&lt;/h3&gt;

&lt;p&gt;No need for external tools — images can now be edited directly within Automad. The built-in image editor supports cropping, resizing, rotation, filters, watermarks, markups, and color corrections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusable Components
&lt;/h3&gt;

&lt;p&gt;Frequently used content can now be saved as reusable components, making it easy to maintain consistency across an entire site without duplicating work.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Editing Workflow
&lt;/h3&gt;

&lt;p&gt;Starting with Automad 2, all dashboard edits are automatically saved as drafts. This eliminates the need to manually hit "Save". Drafts are only visible to admins and can be reviewed in the in-page editing mode before publishing.    &lt;/p&gt;

&lt;p&gt;Additionally, Automad now supports automatic revision snapshots. When editing a page for an extended period, previous versions are periodically saved in the background, allowing for easy restoration with a few clicks. The trash bin, which previously existed but wasn’t accessible from the dashboard, is now fully integrated — making it simple to restore deleted pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved Page Management
&lt;/h3&gt;

&lt;p&gt;The file tree in the sidebar now supports drag-and-drop for moving and sorting pages. Manual index prefixes are no longer required, and moving pages no longer involves opening modal windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  SMTP Email Configuration
&lt;/h3&gt;

&lt;p&gt;Configuring SMTP for outgoing emails is now supported, with both sendmail and external SMTP servers available as options. Additional configurations may be added in future releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Dashboard Themes
&lt;/h3&gt;

&lt;p&gt;To enhance the user experience, the new dashboard includes three theme options — &lt;em&gt;Light&lt;/em&gt;, &lt;em&gt;Dark&lt;/em&gt;, and &lt;em&gt;Low Contrast&lt;/em&gt; — allowing users to customize the interface to their preference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qmrfo0zbdnyvy947wvp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qmrfo0zbdnyvy947wvp.png" alt="Automad Dark Theme" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the Live Demo
&lt;/h2&gt;

&lt;p&gt;Want to test Automad 2 without setting up a local server? A live demo is available, allowing you to explore all the new features instantly. No signup is required, and each demo instance lasts for one hour.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://try.automad.org" rel="noopener noreferrer"&gt;Click here to start your demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;The alpha version of Automad version 2 can be deployed using Docker or Composer. It is also possible to simply download and install a distribution bundle manually. Note that the new version will require a modern browser, currently Chrome or Firefox, and a server running Apache or Nginx with PHP 8.2+.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

&lt;p&gt;The quickest way to try out Automad version 2 without actually worrying about Nginx and PHP, is using Docker. The v2 tagged image includes everything you need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-dp&lt;/span&gt; 80:80 &lt;span class="nt"&gt;-v&lt;/span&gt; ./app:/app &lt;span class="nt"&gt;--name&lt;/span&gt; mysite automad/automad:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will essentially make your site available at port 80 and mount a directory called app in the current working directory for data persistence. A new user account for the Automad dashboard will be created automatically. The account details will be logged by the running container. You can show these logs using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs mysite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Composer
&lt;/h3&gt;

&lt;p&gt;Alternatively you can install Automad version 2 on your web server using Composer as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project automad/automad &lt;span class="nb"&gt;.&lt;/span&gt; v2.x-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A temporary user account for the user account will be created automatically, which can be changed later. &lt;/p&gt;

&lt;p&gt;Also make sure that the entire directory where Automad is installed is writable by the web server and the PHP process. On Apache, everything should then be pretty much working out of the box. However, in case you are running Nginx, you can use the following server config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;root&lt;/span&gt;    &lt;span class="n"&gt;/path/to/automad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;index&lt;/span&gt;    &lt;span class="s"&gt;index.php&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php&lt;/span&gt;&lt;span class="nv"&gt;$is_args$args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt;   &lt;span class="nf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;fastcgi_index&lt;/span&gt;  &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt;  &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt;  &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;include&lt;/span&gt;        &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manual Installation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Download a distribution bundle and move the unpacked content to the document root of your web server.&lt;/li&gt;
&lt;li&gt;Make sure the PHP process has the permissions to write to the document root and its subdirectories including all installed files.&lt;/li&gt;
&lt;li&gt;Visit the /dashboard route on your site and create the first user.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;There's still more work to be done before &lt;a href="https://automad.org" rel="noopener noreferrer"&gt;Automad&lt;/a&gt; 2 reaches its stable release. A solid beta version is planned soon, which will be production-ready for real-world usage.   &lt;/p&gt;

&lt;p&gt;Enjoy, and stay tuned for more updates!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cms</category>
      <category>opensource</category>
      <category>php</category>
    </item>
  </channel>
</rss>
