<?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: Seppe Gadeyne</title>
    <description>The latest articles on DEV Community by Seppe Gadeyne (@seppegadeyne).</description>
    <link>https://dev.to/seppegadeyne</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F412443%2Fb770a129-9311-40ef-92f1-7cff76ed7c72.jpeg</url>
      <title>DEV Community: Seppe Gadeyne</title>
      <link>https://dev.to/seppegadeyne</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seppegadeyne"/>
    <language>en</language>
    <item>
      <title>Run Qwen3.8-Flash-Next Locally with EXL3 and TabbyAPI on 40+ GB of RAM</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Sun, 04 Oct 2026 17:09:23 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/run-qwen38-flash-next-locally-with-exl3-and-tabbyapi-on-40-gb-of-ram-3m8d</link>
      <guid>https://dev.to/seppegadeyne/run-qwen38-flash-next-locally-with-exl3-and-tabbyapi-on-40-gb-of-ram-3m8d</guid>
      <description>&lt;p&gt;&lt;strong&gt;Qwen3.8-Flash-Next is a 125B mixture-of-experts model, and you can run it locally on one consumer GPU.&lt;/strong&gt; This guide shows the exact route we use on our own workstation: TabbyAPI as the server, ExLlamaV3 as the inference engine, and turboderp's 3.05 bpw EXL3 quant. The machine is a Ryzen 9 9950X3D with an RTX 5090 (32 GB), 64 GB of DDR5-6000, and Arch Linux. Every number below was measured on that machine. Where a figure comes from someone else, I name the source.&lt;/p&gt;

&lt;p&gt;You get an OpenAI-compatible endpoint on &lt;code&gt;localhost&lt;/code&gt; that serves a 262,144-token context at about 50 tokens per second. The trade-off: the model needs more than your GPU alone. About 26 GB sits in VRAM, and about 43 GB of expert weights live in system RAM, where the CPU computes them. That is why the title says 40+ GB of RAM. In practice, plan for 64 GB installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Qwen3.8-Flash-Next is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/Qwen/Qwen3.8-Flash-Next" rel="noopener noreferrer"&gt;Qwen3.8-Flash-Next&lt;/a&gt; is an open-weight model from the Qwen team. The model card lists 125B parameters, of which about 6B are active per token, plus a 51B n-gram embedding table and a 4B multi-token prediction (MTP) layer. Each of the 48 layers has 512 routed experts, and 10 of them are picked per token.&lt;/p&gt;

&lt;p&gt;That design makes it a good fit for a single GPU with CPU offload. Only a small part of the model does work for each token, so the experts that are rarely used can sit in system RAM without slowing every step. The n-gram table is a lookup, not a compute step, and ExLlamaV3 can stream it from disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why EXL3 and TabbyAPI
&lt;/h2&gt;

&lt;p&gt;EXL3 is the quantization format of &lt;a href="https://github.com/turboderp-org/exllamav3" rel="noopener noreferrer"&gt;ExLlamaV3&lt;/a&gt;. According to the project README, it is a streamlined variant of &lt;a href="https://github.com/Cornell-RelaxML/qtip" rel="noopener noreferrer"&gt;QTIP&lt;/a&gt;, a trellis-based method from Cornell RelaxML. The practical gain is quality per bit. On the &lt;a href="https://huggingface.co/turboderp/Qwen3.8-Flash-Next-exl3" rel="noopener noreferrer"&gt;model page of the EXL3 quants&lt;/a&gt;, turboderp publishes a KL divergence chart for this model. The 3.05 bpw quant scores 0.0177 there, against 0.0349 for the UD-IQ3_XXS GGUF quant at a similar size. Lower means closer to the original model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/theroyallab/tabbyAPI" rel="noopener noreferrer"&gt;TabbyAPI&lt;/a&gt; is the official API server for ExLlamaV3. It exposes an OpenAI-compatible API, so editors, chat clients, and agent frameworks that speak that API can use your local model without code changes. It also wires up the ExLlamaV3 options this model needs: CPU expert offload, the n-gram table, and MTP drafting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware requirements
&lt;/h2&gt;

&lt;p&gt;This is the hardware we measured on, and what the model used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPU: RTX 5090 with 32 GB of VRAM. About 26 GB in use with the config below.&lt;/li&gt;
&lt;li&gt;System RAM: 64 GB of DDR5-6000. About 43 GB resident for the CPU expert arena.&lt;/li&gt;
&lt;li&gt;CPU: Ryzen 9 9950X3D (16 cores, 32 threads). The CPU computes the cold experts, so core count matters.&lt;/li&gt;
&lt;li&gt;Disk: an NVMe drive with room for the 85.1 GB download. The n-gram table is read from it during inference.&lt;/li&gt;
&lt;li&gt;OS: Arch Linux with a current NVIDIA driver.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two things did not work well. With 60 GB of RAM it gets tight as soon as a browser and other heavy programs run next to the model. And once part of the arena lands in zram or swap, decoding collapses, as described in the pitfalls below. Treat 64 GB as the realistic minimum for this configuration, and run the model on a machine that is not doing other heavy work at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install TabbyAPI with the prebuilt exllamav3 wheel
&lt;/h2&gt;

&lt;p&gt;Clone TabbyAPI and create a Python 3.11 virtual environment inside it:&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/theroyallab/tabbyAPI
&lt;span class="nb"&gt;cd &lt;/span&gt;tabbyAPI
python3.11 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="s2"&gt;".[cu12]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cu12&lt;/code&gt; extra installs PyTorch 2.9.0 (CUDA 12.8) and the prebuilt exllamav3 1.5.1 wheel for your Python version. At the time of writing, TabbyAPI's main branch pins exllamav3 1.5.1. Check that both landed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip show exllamav3 torch | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(Name|Version)'&lt;/span&gt;
python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import torch; print(torch.cuda.is_available())"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;exllamav3&lt;/code&gt; at &lt;code&gt;1.5.1+cu128.torch2.9.0&lt;/code&gt; and &lt;code&gt;True&lt;/code&gt; for CUDA.&lt;/p&gt;

&lt;p&gt;Use the prebuilt wheel rather than a just-in-time (JIT) build. The wheel is faster to set up, and on our system the JIT build of the CUDA extension broke on GCC 16. That build only went through after adding &lt;code&gt;-Xcompiler -Wno-template-body&lt;/code&gt; to the compiler flags. With the wheel you skip the whole problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Download the 3.05 bpw EXL3 quant
&lt;/h2&gt;

&lt;p&gt;The quants live in one Hugging Face repository, with one branch per size. This guide uses the &lt;code&gt;3.05bpw_h5_ng5&lt;/code&gt; revision: 3.05 bits per weight for the experts, a 5-bit output head, and a 5-bit n-gram table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hf download turboderp/Qwen3.8-Flash-Next-exl3 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--revision&lt;/span&gt; 3.05bpw_h5_ng5 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--local-dir&lt;/span&gt; models/qwen3.8-flash-next-exl3-3.05bpw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;hf&lt;/code&gt; command comes with &lt;code&gt;huggingface_hub&lt;/code&gt;, which TabbyAPI installs into the venv. Older guides use &lt;code&gt;huggingface-cli download&lt;/code&gt;, but recent &lt;code&gt;huggingface_hub&lt;/code&gt; versions no longer run that command. TabbyAPI also has its own downloader (&lt;code&gt;./start.sh download &amp;lt;repo&amp;gt; --revision &amp;lt;branch&amp;gt;&lt;/code&gt;) if you prefer that.&lt;/p&gt;

&lt;p&gt;The download is 85.1 GB. One file stands out: &lt;code&gt;ngram_embedding.safetensors&lt;/code&gt; is 32.6 GB on its own. Keep the folder on NVMe, because that table is streamed from disk while the model runs. The folder name becomes the model name in TabbyAPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure config.yml and a sampling preset
&lt;/h2&gt;

&lt;p&gt;Copy &lt;code&gt;config_sample.yml&lt;/code&gt; to &lt;code&gt;config.yml&lt;/code&gt; and change the keys below. The rest can stay at the defaults. These are the values we run and measured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
 &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
 &lt;span class="na"&gt;disable_auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;model_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;models&lt;/span&gt;
 &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qwen3.8-flash-next-exl3-3.05bpw&lt;/span&gt;
 &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exllamav3&lt;/span&gt;
 &lt;span class="na"&gt;max_seq_len&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;262144&lt;/span&gt;
 &lt;span class="na"&gt;cache_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;262144&lt;/span&gt;
 &lt;span class="na"&gt;cache_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Q8&lt;/span&gt;
 &lt;span class="na"&gt;gpu_split_auto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
 &lt;span class="na"&gt;autosplit_reserve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;1000&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
 &lt;span class="na"&gt;cpu_moe_split_experts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;
 &lt;span class="na"&gt;cpu_moe_threads&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt;
 &lt;span class="na"&gt;ngram_ram&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
 &lt;span class="na"&gt;reasoning&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;draft_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;draft_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mtp&lt;/span&gt;
 &lt;span class="na"&gt;draft_num_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
 &lt;span class="na"&gt;dynamic_draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;sampling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;override_preset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qwen3_8_flash_next&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the important keys do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;backend: exllamav3&lt;/code&gt; selects the engine. The value is &lt;code&gt;exllamav3&lt;/code&gt;, not &lt;code&gt;exl3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cpu_moe_split_experts: 400&lt;/code&gt; keeps the 400 coldest of the 512 experts per layer in system RAM and computes them on the CPU. The hot experts stay in VRAM, and ExLlamaV3 adjusts the placement while it runs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cpu_moe_threads: 24&lt;/code&gt; sets the CPU worker threads for those experts. Without it, ExLlamaV3 uses half the core count.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ngram_ram: false&lt;/code&gt; streams the 51B n-gram table from disk. Loading it into RAM would need tens of gigabytes more than a 64 GB machine has left.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache_mode: Q8&lt;/code&gt; stores the KV cache in 8 bits instead of 16, which halves its size for the full 262,144-token context.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autosplit_reserve: [1000]&lt;/code&gt; leaves 1,000 MB of VRAM free on the GPU for the desktop and CUDA overhead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;draft_mode: mtp&lt;/code&gt; uses the model's own MTP layer for speculative decoding. No separate draft model is needed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disable_auth: true&lt;/code&gt; is only acceptable because the server listens on &lt;code&gt;127.0.0.1&lt;/code&gt;. If you open it to your network, turn authentication back on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now create the sampling preset in &lt;code&gt;sampler_overrides/qwen3_8_flash_next.yml&lt;/code&gt;. TabbyAPI finds it by name in that folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;
 &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;top_k&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
 &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;top_p&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.95&lt;/span&gt;
 &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;min_p&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
 &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values are fallbacks. A request that sends its own sampler settings still overrides them. Skipping this preset is one of the pitfalls below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the server and send a first request
&lt;/h2&gt;

&lt;p&gt;Start TabbyAPI from the project folder with the venv active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From a warm NVMe, the model is loaded in about two minutes on our machine. A first load from a cold disk can take longer. Check that the server is up and the model is loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:5000/health
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:5000/v1/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then send a first chat request to the OpenAI-compatible endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:5000/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
 "model": "qwen3.8-flash-next-exl3-3.05bpw",
 "messages": [{"role": "user", "content": "Write a Python function that checks whether a string is a palindrome."}],
 "max_tokens": 4000
 }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model reasons before it answers. TabbyAPI splits that reasoning into a separate &lt;code&gt;reasoning_content&lt;/code&gt; field, and the answer arrives in &lt;code&gt;content&lt;/code&gt;. Note the high &lt;code&gt;max_tokens&lt;/code&gt;. The reason is explained in the pitfalls.&lt;/p&gt;

&lt;p&gt;The server log prints the speed per request, and with MTP drafting also the acceptance rate of the drafted tokens. That log is the source for all the measurements in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance tuning: our measured A/B results
&lt;/h2&gt;

&lt;p&gt;We tuned three settings one at a time, with greedy decoding, reading the figures from the TabbyAPI server log. All runs were done with nothing else heavy running on the machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU threads for the expert arena
&lt;/h3&gt;

&lt;p&gt;The thread count for the CPU experts had the biggest effect on decoding. More threads is not always better: at 32 threads the SMT siblings compete for the same cores, and decoding got slower than at 24.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decode speed by cpu_moe_threads (median, tokens per second)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cpu_moe_threads&lt;/th&gt;
&lt;th&gt;Decode (tok/s)&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;48.1&lt;/td&gt;
&lt;td&gt;Fewer threads than the CPU can feed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;51.7&lt;/td&gt;
&lt;td&gt;Best result; prefill 188 tok/s in this run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;47.6&lt;/td&gt;
&lt;td&gt;Slower due to SMT contention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On a 16-core, 32-thread CPU, 24 threads was the sweet spot. On a different CPU, run the same test with your own core count.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many experts go to the CPU
&lt;/h3&gt;

&lt;p&gt;In a separate A/B run, we lowered &lt;code&gt;cpu_moe_split_experts&lt;/code&gt; from 430 to 400. That puts 30 more experts per layer on the GPU. Decoding stayed the same, prefill rose from 174 to 204 tokens per second, and the machine kept about 2 GB more free RAM. With 32 GB of VRAM, 400 fit with the 1,000 MB reserve. On a card with less VRAM you need a higher value, which means more RAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Draft depth for MTP speculative decoding
&lt;/h3&gt;

&lt;p&gt;With MTP drafting, the model proposes several tokens ahead and verifies them in one pass. &lt;code&gt;draft_num_tokens: 4&lt;/code&gt; with &lt;code&gt;dynamic_draft: true&lt;/code&gt; gave the best result. A draft depth of 6 was 4% slower. The acceptance rate stayed around 56–58%, so longer drafts mostly produced more tokens that were thrown away.&lt;/p&gt;

&lt;h3&gt;
  
  
  The end result on our machine
&lt;/h3&gt;

&lt;p&gt;With all three settings in place, we see about 48–52 tokens per second for decoding and about 175–205 tokens per second for prefill. The model loads in about two minutes from a warm NVMe. These figures were measured without other heavy workloads running.&lt;/p&gt;

&lt;p&gt;For context: the &lt;a href="https://github.com/turboderp-org/exllamav3/releases/tag/v1.5.0" rel="noopener noreferrer"&gt;ExLlamaV3 1.5.0 release notes&lt;/a&gt; list higher numbers for this model on an RTX 5090 paired with a Threadripper 7960X, with the n-gram table in RAM and a pinned arena. That is different hardware with more memory and different settings, so do not treat our numbers as the ceiling, or theirs as what a 64 GB desktop will reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls we ran into
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Swap kills decoding
&lt;/h3&gt;

&lt;p&gt;If part of the expert arena is pushed into zram or swap, decoding falls from about 50 to about 2.4 tokens per second. The model keeps running, so it is easy to miss. Check how much of the process sits in swap:&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;grep &lt;/span&gt;VmSwap /proc/&lt;span class="si"&gt;$(&lt;/span&gt;pgrep &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"python main.py"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything far above zero means the arena no longer fits. Close other programs, keep an eye on free RAM with &lt;code&gt;free -g&lt;/code&gt;, and do not start the model next to other memory-hungry work.&lt;/p&gt;

&lt;h3&gt;
  
  
  No sampling preset, no stable output
&lt;/h3&gt;

&lt;p&gt;Without a sampling preset, requests that send no sampler values run with top_k 0 and top_p 1. In that state the model can drift into other languages mid-answer. The preset from step 03 (temperature 0.7, top_k 20, top_p 0.95) works well and keeps clients that send nothing on safe values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasoning eats max_tokens
&lt;/h3&gt;

&lt;p&gt;Qwen3.8-Flash-Next thinks before it writes the answer, and those reasoning tokens count toward &lt;code&gt;max_tokens&lt;/code&gt;. With a low limit, the response can stop before the actual answer starts, leaving &lt;code&gt;content&lt;/code&gt; empty. Use at least 400 tokens, and 4,000 for real tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Small config mistakes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;backend&lt;/code&gt; must be &lt;code&gt;exllamav3&lt;/code&gt;; &lt;code&gt;exl3&lt;/code&gt; is not a valid value. And run this model on its own: a second model, a game, or another GPU workload on the same machine takes VRAM or RAM the model needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it gets you
&lt;/h2&gt;

&lt;p&gt;The result is a strong coding model on hardware you control. On the &lt;a href="https://huggingface.co/Qwen/Qwen3.8-Flash-Next" rel="noopener noreferrer"&gt;Qwen model card&lt;/a&gt;, Qwen3.8-Flash-Next scores 58.7 on DeepSWE 1.1 and 62.5 on SWE-bench Pro. Those are the Qwen team's figures for the full model, not measurements of this quant. In our own comparison, it is the strongest model for coding tasks that we can run on this consumer machine. Nothing leaves your network, there is no per-token bill, and any tool that speaks the OpenAI API can connect, from a code editor to an AI agent running a workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives worth considering
&lt;/h2&gt;

&lt;p&gt;This setup is not the only route, and it is not always the best one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A smaller quant. The same repository has a 2.05 bpw version that needs less memory, at a clear cost in quality: turboderp's chart shows a KL divergence of 0.0684 for it, against 0.0177 for 3.05 bpw. We did not measure its speed or memory use.&lt;/li&gt;
&lt;li&gt;GGUF with llama.cpp. This route runs on more hardware, including Macs and CPU-only machines, and Unsloth publishes &lt;a href="https://unsloth.ai/docs/models/qwen3.8-next" rel="noopener noreferrer"&gt;GGUF quants with a run guide&lt;/a&gt;. In our comparison, that route was slower on this model than ExLlamaV3.&lt;/li&gt;
&lt;li&gt;A cloud API. If you do not have the hardware, or only need the model now and then, a hosted API costs less than a new GPU and a RAM upgrade. You give up the privacy and fixed costs of a local setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The short recipe: TabbyAPI in a Python 3.11 venv with the prebuilt exllamav3 1.5.1 wheel, the &lt;code&gt;3.05bpw_h5_ng5&lt;/code&gt; quant on NVMe, &lt;code&gt;backend: exllamav3&lt;/code&gt;, 400 experts per layer on the CPU with 24 threads, a Q8 cache, MTP drafting with 4 tokens, and a sampling preset. Watch swap, give reasoning room, and run the model on its own. On one RTX 5090 with 64 GB of RAM, that gives a 125B model at about 50 tokens per second. ExLlamaV3 and TabbyAPI move quickly, so check the &lt;a href="https://github.com/turboderp-org/exllamav3/releases" rel="noopener noreferrer"&gt;ExLlamaV3 releases&lt;/a&gt; for new versions before you start.&lt;/p&gt;

&lt;p&gt;If you are wondering what hardware you actually need before AI can run locally in your business, we wrote a practical guide about that too: &lt;a href="https://voltti.ai/en/insights/running-ai-locally-what-computer-do-you-really-need" rel="noopener noreferrer"&gt;What computer do you really need to run AI locally?&lt;/a&gt; — it covers the trade-offs between local and cloud AI for business use.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>localllm</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A website without a CMS: Astro Content Collections in practice</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Thu, 01 Oct 2026 07:42:14 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/a-website-without-a-cms-astro-content-collections-in-practice-17</link>
      <guid>https://dev.to/seppegadeyne/a-website-without-a-cms-astro-content-collections-in-practice-17</guid>
      <description>&lt;p&gt;I moved &lt;a href="https://straffesites.com/en" rel="noopener noreferrer"&gt;straffesites.com&lt;/a&gt; from Astro with Storyblok to Astro Content Collections with local MDX and JSON files. The site still has articles, case studies, landing pages, and Dutch and English versions. What it no longer has is a separate CMS for managing that content.&lt;/p&gt;

&lt;p&gt;I am the only person managing the content on straffesites.com, and I work with Hermes, an AI agent. Since I already work in Git, moving the content there was a useful simplification. Content changes now go through the same repository and build pipeline as template changes. I can inspect a paragraph edit next to the schema or component it depends on.&lt;/p&gt;

&lt;p&gt;This is a case study of that setup: how the content is organized, what the schemas check, how publishing works, and which CMS features I chose to give up. It is not a recommendation to make every editor learn Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I removed Storyblok from this project
&lt;/h2&gt;

&lt;p&gt;Astro and Storyblok worked well together. I still use that combination for client projects where an editing interface is useful. On straffesites.com, however, I am the only human editing and managing the content. Hermes helps me work with the files and run checks. I did not need a visual editor for that workflow.&lt;/p&gt;

&lt;p&gt;Keeping a CMS meant maintaining content models in another system, managing my CMS account and its permissions, and configuring the integration that fetched content during builds. Code lived in Git; content lived in Storyblok. Changes that touched both required checking both.&lt;/p&gt;

&lt;p&gt;Working with Hermes was another part of the decision. The agent could already read the project, edit files, and run checks. To work with CMS content, it also needed an authenticated client and a separate content workflow. That integration was possible, but it was unnecessary for this project.&lt;/p&gt;

&lt;p&gt;Moving the content into Git removed the CMS-specific access layer. It did not remove repository permissions, deployment credentials, or the need to maintain the AI tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Collections provide a content layer, not an editor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.astro.build/en/guides/content-collections/" rel="noopener noreferrer"&gt;Astro Content Collections&lt;/a&gt; let you organize, validate, and query structured content. They can use local files, remote sources, or live data. The setup on straffesites.com uses local files loaded at build time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MDX for pages and articles.&lt;/li&gt;
&lt;li&gt;JSON for datasets, case metrics, people, and site settings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are six collections in this project. You do not need that exact split to use the same approach.&lt;/p&gt;

&lt;p&gt;A collection combines a loader with a schema. The loader finds the entries; the schema defines the fields the templates can expect. Here is an excerpt from the article collection configuration:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reference&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="s1"&gt;astro:content&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;glob&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="s1"&gt;astro/loaders&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;articleFrontmatterSchema&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="s1"&gt;./content/schemas/article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.mdx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/content/articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;articleFrontmatterSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeExtend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;people&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;superRefine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;articleType&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glossary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addIssue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog and case articles must include an author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// The full configuration also registers the people collection.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="cm"&gt;/*, pages, datasets, caseMetrics, people, settings */&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a project-specific excerpt, not a complete starter configuration. It imports the article schema from another file, and the author reference depends on the &lt;code&gt;people&lt;/code&gt; collection being registered in the full configuration.&lt;/p&gt;

&lt;p&gt;The same article collection holds blog posts, case studies, and glossary entries. The refinement requires an author for blog posts and cases while allowing glossary entries without one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What validation catches
&lt;/h3&gt;

&lt;p&gt;The frontmatter schema defines fields such as the title, route, publication date, category, tags, summary, and FAQ items. Required fields must be present. A strict schema also rejects unknown field names, so a typo cannot silently become unused metadata.&lt;/p&gt;

&lt;p&gt;That gives the templates a defined content contract. It does not make the article correct.&lt;/p&gt;

&lt;p&gt;A schema can reject a missing title or a value that violates a configured rule. It cannot establish that an explanation is accurate, a source supports a claim, or an internal link is helpful. A nonempty summary also does not imply a sensible length unless you add a length limit.&lt;/p&gt;

&lt;p&gt;I use additional contract tests and build-output checks for rules that go beyond frontmatter. Content Collections alone do not check every link, component choice, or editorial requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you lose without a CMS
&lt;/h3&gt;

&lt;p&gt;There is no visual editor in this local-file setup. Content Collections do not provide editor accounts, approval screens, a media library, or a publishing calendar.&lt;/p&gt;

&lt;p&gt;You can add previews and access controls around a Git workflow. You can also put a Git-based CMS in front of the files. That is a valid option, but it adds an editing interface; it is different from editing the files directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I organize the content
&lt;/h2&gt;

&lt;p&gt;The MDX files on straffesites.com are grouped by language and content type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/content/
├── pages/            # landing pages and hub pages (MDX)
│   ├── nl/           # Dutch-language pages
│   └── en/           # English pages
└── articles/
    ├── nl/
    │   ├── blog/      # blogs
    │   ├── case/      # case studies
    │   └── begrippen/ # glossary terms
    └── en/
        ├── blog/      # blogs
        ├── case/      # case studies
        └── glossary/ # glossary terms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON collections sit alongside these directories. The tree above only shows the MDX content.&lt;/p&gt;

&lt;p&gt;Each MDX entry starts with frontmatter. The body uses Markdown inside typed content-block components. On the original website, for example, this article's source file contains its metadata and body, while the page template renders shared elements such as the author card and related articles. The DEV version you are reading is an adapted Markdown crosspost, not that MDX file running inside Astro.&lt;/p&gt;

&lt;p&gt;For this project, the route comes from an explicit frontmatter field rather than the file's location. That lets me reorganize source files without treating every move as a URL change. The templates and tests still need to respect that convention; Astro does not infer the project's routing rules from the directory tree alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping translations connected
&lt;/h3&gt;

&lt;p&gt;Dutch and English pages have a shared pair registry in the repository. It records which routes are counterparts.&lt;/p&gt;

&lt;p&gt;Build checks can then verify that those routes exist and that the relationships are consistent. The files being next to each other is not enough: translation pairing is a rule I have to define and test.&lt;/p&gt;

&lt;p&gt;This is one advantage of keeping content and code together. The content, route relationships, and checks can change in the same diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing is a deployment workflow
&lt;/h2&gt;

&lt;p&gt;On straffesites.com, a push to &lt;code&gt;main&lt;/code&gt; triggers a Vercel build and deployment. Astro validates the content and renders static pages. A local commit by itself does not publish anything.&lt;/p&gt;

&lt;p&gt;The workflow is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit MDX or JSON
    -&amp;gt; inspect the diff
    -&amp;gt; run content and build checks
    -&amp;gt; commit and push
    -&amp;gt; verify the deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That diagram describes the work to do, not a security guarantee. Git does not force someone to inspect a diff, and a schema does not require human approval. Repository permissions and required checks determine what can reach production.&lt;/p&gt;

&lt;p&gt;Git also makes rollback understandable: revert the relevant change, build again, and deploy the result. Reverting a commit locally does not change the live site until the reverted version is deployed successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  The maintenance moves rather than disappearing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Local content in Git&lt;/th&gt;
&lt;th&gt;API-first headless CMS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Editing&lt;/td&gt;
&lt;td&gt;Files and repository tooling&lt;/td&gt;
&lt;td&gt;CMS dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content validation&lt;/td&gt;
&lt;td&gt;Configured schemas and project checks&lt;/td&gt;
&lt;td&gt;CMS field rules; project checks can also apply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content history&lt;/td&gt;
&lt;td&gt;Diffs for committed changes&lt;/td&gt;
&lt;td&gt;CMS revisions, depending on the platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static publishing&lt;/td&gt;
&lt;td&gt;Build and deploy after a repository change&lt;/td&gt;
&lt;td&gt;Build and deploy after a CMS content change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Systems to maintain&lt;/td&gt;
&lt;td&gt;Dependencies, CI, hosting, and repository access&lt;/td&gt;
&lt;td&gt;Those systems plus the CMS integration and accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are common workflows, not fixed limits. A headless CMS can feed a fully static site, and a Git-based CMS can provide a dashboard while keeping content in files.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I work with Hermes
&lt;/h2&gt;

&lt;p&gt;Hermes is the AI agent I work with on this project, not another human editor. It can read an MDX file, update a paragraph, and run the same checks used for code changes. It does not need a CMS-specific API key to edit checked-out files.&lt;/p&gt;

&lt;p&gt;For me, the useful part is how easy the changes are to inspect. A content edit can include its frontmatter, body, and related template changes in one diff. There is no separate CMS revision to reconstruct alongside the code change.&lt;/p&gt;

&lt;p&gt;Reading a local file avoids a CMS API request. It does not make model usage free: text sent to the model still consumes tokens. Nor does it prove that agents perform better with files than with a CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instructions are not access controls
&lt;/h3&gt;

&lt;p&gt;Project instructions can tell an agent which files to use, which commands to run, and what it must not change. They help it work within the project's conventions. They are not a security boundary.&lt;/p&gt;

&lt;p&gt;An agent with broad repository or deployment access can still make harmful changes. Limit its permissions, make checks explicit, and verify what it changed. For straffesites.com, I remain responsible for the scope and the published result, including when I authorize Hermes to deploy after checks.&lt;/p&gt;

&lt;p&gt;Plausible but false prose can pass every schema check. That is an editorial review problem, not something Zod can solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing a CMS does not automatically make a website faster
&lt;/h2&gt;

&lt;p&gt;I track straffesites.com through monthly PageSpeed measurements and Search Console data on its &lt;a href="https://straffesites.com/en/case/straffe-sites" rel="noopener noreferrer"&gt;public case page&lt;/a&gt;. Those measurements describe the deployed site. They are not a controlled test of whether removing Storyblok improved performance.&lt;/p&gt;

&lt;p&gt;A headless CMS can supply content during the build and produce the same static HTML. Visitors do not necessarily make a CMS request when they open that page.&lt;/p&gt;

&lt;p&gt;Removing the CMS API from my build reduced an external build dependency. Visitor performance still depends on the output: JavaScript, images, fonts, caching, and how the page renders.&lt;/p&gt;

&lt;p&gt;The change I can describe directly is operational. My content workflow no longer needs a CMS service, a separate editor account, or a CMS integration. Dependencies, hosting, and access control still need maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I would keep the CMS
&lt;/h2&gt;

&lt;p&gt;For a client project, I would keep a CMS when the editing interface solves a problem the client or their editorial team actually has.&lt;/p&gt;

&lt;p&gt;If editors do not work in Git, asking them to maintain MDX may just move the burden onto them or the development team. Draft states, permissions, scheduling, and media management are useful features, especially when several people publish regularly.&lt;/p&gt;

&lt;p&gt;Translation workflows can also be more involved than maintaining two files and a route pair. Editors may need field-level translation status or separate approval steps. A CMS can provide those workflows without requiring the team to build them.&lt;/p&gt;

&lt;p&gt;Changing a content model is another consideration. In my setup, adding a new content type means changing a schema and its templates. A CMS dashboard may make field management easier, although frontend changes can still require development work.&lt;/p&gt;

&lt;p&gt;You can build versions of these features around Git. But if you need an editor, scheduling, media permissions, and approval screens, consider whether you are rebuilding the CMS you removed.&lt;/p&gt;

&lt;p&gt;For straffesites.com, direct file editing fits how I manage the content with help from Hermes. For a client with an editorial team, I may choose a CMS instead. I would start with who edits the content and what their publishing process needs, then choose the storage and tooling.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted for DEV from &lt;a href="https://straffesites.com/en/blog/website-without-cms" rel="noopener noreferrer"&gt;A website without a CMS: Astro Content Collections in practice&lt;/a&gt;, originally published on straffesites.com.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>cms</category>
      <category>git</category>
    </item>
    <item>
      <title>Automating SEO with AI: a self-improving content loop</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Mon, 28 Sep 2026 11:52:53 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/automating-seo-with-ai-a-self-improving-content-loop-2aka</link>
      <guid>https://dev.to/seppegadeyne/automating-seo-with-ai-a-self-improving-content-loop-2aka</guid>
      <description>&lt;p&gt;Everyone agrees you should "let AI do your SEO." Few people explain what that looks like when you actually build it. This is the setup I run on my own site: an AI agent with read-only access to Google Search Console, a dedicated service account via gcloud, and one small, verified improvement per run — every thirty days.&lt;/p&gt;

&lt;p&gt;The article below is a crosspost from my site. It covers the full loop: setup, prioritization, one-change-per-run, measurement, and the honest learning curve before anything runs on its own.&lt;/p&gt;

&lt;p&gt;Automating SEO with AI, in practice, means: an AI agent with read access to your Google Search Console that pulls the real search queries from your data every thirty days and makes one small, verified improvement to your pages. That is not a robot that lifts your website to the top of Google while you drink coffee — that robot does not exist. It is a loop: pull data, prioritize, improve, measure, and again thirty days later. Not everything at once, not on a hunch — one thing per run, measured and logged.&lt;/p&gt;

&lt;p&gt;That approach works because SEO is not a one-time project but a habit. Anyone who looks at the same data every month and makes one targeted intervention each time will see their pages gradually align better with what people actually search for. The agent makes that habit affordable: it does the heavy lifting — pulling data, prioritizing, preparing — while you make the decisions. At first you supervise every run. After a few months, most of it runs automatically on a &lt;a href="https://straffesites.com/en/glossary/cron" rel="noopener noreferrer"&gt;cron&lt;/a&gt; job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why automating SEO with AI makes sense now
&lt;/h2&gt;

&lt;p&gt;An agent like Hermes can query an API, read and write files, run a build, and inspect the result. For recurring GSC analysis, I combine those tools with a dedicated service account: gcloud handles the Cloud setup, and Search Console grants that account read access to the property. The agent can then retrieve data without waiting for me to sign in through a browser again.&lt;/p&gt;

&lt;p&gt;SEO is an ideal application for this, for three reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The source data is objective. Search Console shows literally how people find you: queries, impressions, clicks, positions. That gives the agent source data, but does not prevent hallucinations or incorrect interpretation. Check its conclusions against the export; anonymized queries and API limits make the data incomplete.&lt;/li&gt;
&lt;li&gt;The feedback loop already exists. Thirty days after every change you see in the same data whether it had an effect. That is a perfect learning loop for a step-by-step process.&lt;/li&gt;
&lt;li&gt;The work is repetitive but requires judgment. Pulling and organizing data is pure routine. Interpreting and prioritizing takes judgment. That exact mix is where a supervised agent shines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Done manually, most people pick this up once a quarter with a spreadsheet and good intentions. The agent turns it into a fixed rhythm — and rhythm is the most underrated success factor in SEO.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need: gcloud, GSC access, and an agent
&lt;/h2&gt;

&lt;p&gt;For scheduled SEO runs, I use gcloud with a dedicated service account. The agent gets its own Google identity, added as a read-only user to the Search Console property, rather than relying on my personal login. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Google Cloud project and gcloud, with an administrator who can enable the Search Console API and create the service account.&lt;/li&gt;
&lt;li&gt;A Search Console property owner who can add that account under Users and permissions.&lt;/li&gt;
&lt;li&gt;An agent that can use tools, such as &lt;a href="https://hermes-agent.nousresearch.com/docs" rel="noopener noreferrer"&gt;Hermes from Nous Research&lt;/a&gt;, Claude Code, Cursor agents, or your own application around a language model.&lt;/li&gt;
&lt;li&gt;A git repository for small, reviewable website changes, separate from the Google credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a dedicated service account with gcloud
&lt;/h3&gt;

&lt;p&gt;Run these setup commands as a human administrator with the required Cloud permissions. Replace &lt;code&gt;YOUR_PROJECT_ID&lt;/code&gt; with your existing project ID; the example account name is &lt;code&gt;seo-reader&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;searchconsole.googleapis.com iam.googleapis.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_PROJECT_ID
gcloud iam service-accounts create seo-reader &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--display-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"SEO read-only access"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_PROJECT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The account's email address will be &lt;code&gt;seo-reader@YOUR_PROJECT_ID.iam.gserviceaccount.com&lt;/code&gt;. Do not give it broad Cloud Owner or Editor roles. A Cloud IAM role does not grant access to your Search Console property; you add that separately. If it needs permission to consume the quota project, grant the narrow Service Usage Consumer role there, not project-wide administration. See &lt;a href="https://docs.cloud.google.com/iam/docs/service-accounts-create" rel="noopener noreferrer"&gt;Google's service account setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the service account as a restricted GSC user
&lt;/h3&gt;

&lt;p&gt;In Search Console, the property owner opens the exact property, then &lt;strong&gt;Settings → Users and permissions → Add user&lt;/strong&gt;. Enter the service account's full email address, choose &lt;strong&gt;Restricted&lt;/strong&gt;, and add it. You do not need to make the account an owner or verify the domain again. Google's &lt;a href="https://support.google.com/webmasters/answer/7687615?hl=en" rel="noopener noreferrer"&gt;permissions table&lt;/a&gt; confirms that Restricted users can read the Performance report, which is what this loop needs.&lt;/p&gt;

&lt;p&gt;Configure the API client to request &lt;code&gt;https://www.googleapis.com/auth/webmasters.readonly&lt;/code&gt;, as documented in &lt;a href="https://developers.google.com/webmaster-tools/v1/how-tos/authorizing" rel="noopener noreferrer"&gt;Search Console authorization&lt;/a&gt;. The property role limits what the account can do; the OAuth scope limits what its API token can do. That scope is not tied to one website, so add the account only to properties the workflow needs. A domain property uses an identifier such as &lt;code&gt;sc-domain:example.com&lt;/code&gt;; a URL-prefix property uses its exact URL prefix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run without a recurring personal login
&lt;/h3&gt;

&lt;p&gt;Use an attached service account or Workload Identity Federation when the runtime and GSC client support it. These avoid storing a long-lived private key. If your local runner needs a JSON key, create one in Google Cloud under the service account's &lt;strong&gt;Keys → Add key → Create new key&lt;/strong&gt;. Follow your organization's key policy and store the file outside your repository. The following path is a placeholder for that protected file:&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;chmod &lt;/span&gt;600 /secure/path/seo-reader.json
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/secure/path/seo-reader.json"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_QUOTA_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_PROJECT_ID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set these variables in the environment that actually starts the agent or its GSC connector, including scheduled runs. A cron job does not automatically inherit your interactive shell's exports. Use a client that supports &lt;a href="https://docs.cloud.google.com/docs/authentication/application-default-credentials" rel="noopener noreferrer"&gt;Application Default Credentials (ADC)&lt;/a&gt;, with the read-only scope above. ADC locates the credentials; it does not choose the permissions for you. ADC and the gcloud CLI also use separate credential configurations: being logged into gcloud does not configure the application's credentials.&lt;/p&gt;

&lt;p&gt;Google's authentication library obtains and renews short-lived access tokens for the service account without an interactive browser login. Personal OAuth can also refresh tokens, but it ties the job to a person's authorization. A service account removes that dependency, making unattended runs easier to maintain. It is not maintenance-free: rotate keys, revoke unused credentials, and review access. Keep keys and tokens out of prompts, logs, Git, and browser code; Google's &lt;a href="https://docs.cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys" rel="noopener noreferrer"&gt;key-management guidance&lt;/a&gt; explains the risks.&lt;/p&gt;

&lt;p&gt;Before scheduling the loop, run a small Performance-data query through the same client and environment the scheduled job will use. Confirm the intended property and service account, and stop if access fails rather than falling back to your personal credentials. Isolate the runner from other credentials too. Website changes still go through your codebase and review flow; GSC read access does not authorize publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop: pull the data
&lt;/h2&gt;

&lt;p&gt;Every run starts the same way: the agent pulls fresh Search Console data for a bounded period — say the last thirty days, or a comparison with the thirty days before that. It requests the top queries per page, with impressions, clicks, CTR, and average position, and writes that away as a structured file in the repo. From that moment it no longer works with live APIs but with a snapshot you can inspect and compare against previous runs.&lt;/p&gt;

&lt;p&gt;One important detail: Search Console data only becomes final after a few days. Google itself says about fresh data points: "Each fresh data point will be replaced with the final data point after a few days" (&lt;a href="https://developers.google.com/search/blog/2019/09/search-performance-fresh-data" rel="noopener noreferrer"&gt;Google Search Central Blog on data freshness&lt;/a&gt;). Measure too early and you draw conclusions from numbers that still shift. That is why the agent works with closed periods and preferably compares month over month, never day over day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritize: where is the biggest win?
&lt;/h2&gt;

&lt;p&gt;With the data on the table, the agent looks for patterns worth a decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many impressions, few clicks on a query your page already ranks for: usually a title or meta description problem, not a content problem.&lt;/li&gt;
&lt;li&gt;Position 8 to 20 with a declining trend: the page is relevant enough to rank but not convincing enough to break through. Sharpening the search intent and internal links helps here.&lt;/li&gt;
&lt;li&gt;Queries you have no good page for: content gaps. Those lead to a new article or an expansion of an existing page — not keyword stuffing on some random page.&lt;/li&gt;
&lt;li&gt;Multiple pages competing for the same query: cannibalization. Strengthen one page, give the other its own angle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each finding the agent states a hypothesis: this page, this query, this expected intervention, this expected effect. No more than one or two per run. If you change ten things at once, you will never know what worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make one small improvement
&lt;/h2&gt;

&lt;p&gt;Then comes the human moment. The agent prepares the change — a rewritten intro that puts the real search intent first, a better title tag, an internal link from a strong page — and for now you always review it. The change is deliberately small: one page, one aspect, describable in one sentence. "This intro now promises what the searcher actually asks for." Not: "The entire page optimized."&lt;/p&gt;

&lt;p&gt;Next, the agent verifies its own work. For a website in git that means: commit the change, run the build, fetch the changed route again, and confirm the promised text is actually there. A successful tool call is not a successful task; the run is only complete when the published page checks out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure and log
&lt;/h2&gt;

&lt;p&gt;Every run ends in a log: what the agent saw in the data, what it changed, what the expected effect is, and when to check it. Ideally in your issue tracker, so that thirty days later an on-the-record comparison is possible. Did the intervention raise the CTR on that one query? Did the position move from 14 to 9? Or did nothing happen — and why not?&lt;/p&gt;

&lt;p&gt;That logging is not administrative luxury. It is the only way to learn from the agent and to teach the agent. Because every time a hypothesis does not pan out, it says something about your instructions: was the prioritization wrong, the intervention too small, or the expectation too high?&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeat every 30 days
&lt;/h2&gt;

&lt;p&gt;A single run does little. The power is in the repetition. After six cycles you have six verified improvements, six measurement moments, and a growing archive of what did and did not work. Along the way your pages have gradually been tuned to the real search intent of your visitors — not to your assumption of what they search for, but to what the data shows they search for.&lt;/p&gt;

&lt;p&gt;Why thirty days and not every week? Because Search Console data only finalizes after several days and positions show too much noise week over week. Thirty days gives a signal you can act on, and it matches the pace of content changes: thorough enough to matter, slow enough to measure cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  From supervising to automatic: be realistic
&lt;/h2&gt;

&lt;p&gt;This needs honesty: &lt;strong&gt;the first time, this does not work&lt;/strong&gt;. The first run of a new agent rarely produces anything publishable. The data gets misinterpreted, the prioritization misses, the proposed copy reads like a committee memo. That is not the concept failing — it is the onboarding period.&lt;/p&gt;

&lt;p&gt;This is an illustrative three-phase plan, not a measured timeline or guarantee. Automate only after repeated tests show that the workflow is reliable enough:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What you do&lt;/th&gt;
&lt;th&gt;What the agent does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Months 1–2&lt;/td&gt;
&lt;td&gt;Supervise every run, correct every change, rewrite instructions&lt;/td&gt;
&lt;td&gt;Pull data, draft the analysis, make mistakes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Months 3–4&lt;/td&gt;
&lt;td&gt;Review results, feed in edge cases&lt;/td&gt;
&lt;td&gt;Prepare complete runs, propose improvements with reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Months 5+&lt;/td&gt;
&lt;td&gt;Check in occasionally, judge escalations&lt;/td&gt;
&lt;td&gt;Run on schedule by itself, ask for human input when in doubt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;How you speed up that curve: give the agent &lt;strong&gt;skills&lt;/strong&gt;. An agent like Hermes works with reusable instruction packages — how to structure a page audit, how to interpret search intent, how to rewrite a title without clickbait. You teach those once, and every next run builds on them. After a few months those skills are the difference between an intern and a colleague: the process is in its fingers, and you only look over its shoulder for the hard cases.&lt;/p&gt;

&lt;p&gt;From that moment on, you turn the loop into a &lt;strong&gt;cron job&lt;/strong&gt;: a scheduled task that runs the agent on a schedule — say the first of every month. The run follows the same rules as when you sat next to it, only without you having to be there. Hard decisions (new pages, major rewrites, anything touching money or reputation) stay explicitly human.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills: teaching the agent how to run SEO audits
&lt;/h2&gt;

&lt;p&gt;The fastest way to climb that curve: give the agent skills — reusable instruction packages that describe one task in detail. A skill for a page audit specifies exactly what data gets pulled (live HTML, Search Console rows, robots.txt), which dimensions get scored (search intent, E-E-A-T, content depth, on-page, structure, technical), with what weights, and in what format the report appears. You write such a package once, or teach it from an existing collection. I've documented that installation route step by step in &lt;a href="https://dev.to/seppegadeyne/hermes-agent-for-seo-from-search-console-to-a-blog-post-1ap0"&gt;Hermes Agent for SEO: from Search Console to a blog post&lt;/a&gt;: installing the agent, connecting Search Console through gcloud, and adapting the SEO skills for it. After that, the agent runs every next audit in exactly the same way — including the cron run in the middle of the night.&lt;/p&gt;

&lt;p&gt;The historical audit below shows what such a report looks like. It was recorded on September 2, 2026, before this article switched to the service account setup. Its earlier authentication advice is preserved as part of the original output; follow the updated setup above for new runs.&lt;/p&gt;

&lt;p&gt;On my own site, this article includes the full recorded audit terminal: the complete first run's output, scores, findings, and the GSC baseline of that moment (recorded on September 2, 2026). It's too long to paste here, but you can read it in &lt;a href="https://straffesites.com/en/blog/automating-seo-with-ai#skills" rel="noopener noreferrer"&gt;the original article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The audit reported that the page had no Search Console data on its publication day instead of inventing numbers. Its scores and baseline describe that moment, not the current page. Each finding comes with a concrete fix. For later changes, record a fresh baseline and wait at least thirty days before assessing the effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an agent may and may not do on its own
&lt;/h2&gt;

&lt;p&gt;Automation without boundaries is a risk, especially on something as visible as your website. A few rules of thumb that work in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read permissions for data, write access through git. The agent reads Search Console, but every change goes through your codebase and review flow.&lt;/li&gt;
&lt;li&gt;Small commits. One change per run, with explanation. Not because the agent cannot do more, but because otherwise you lose the measurement effect.&lt;/li&gt;
&lt;li&gt;No invented numbers. Every metric in copy comes from the actual data export, with period and source attached. An agent that smooths over or invents numbers is useless for this work.&lt;/li&gt;
&lt;li&gt;Human decisions stay human. Merging pages, restructuring key landing pages, changing prices or promises: do not automate.&lt;/li&gt;
&lt;li&gt;Useful stays useful, even at speed. Google's guidance on &lt;a href="https://developers.google.com/search/docs/fundamentals/using-gen-ai-content" rel="noopener noreferrer"&gt;AI-generated content&lt;/a&gt; stresses that automation changes nothing about the requirement that content be made for people and be genuinely helpful — regardless of who or what wrote it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a broader look at deploying such an agent without losing control, read &lt;a href="https://straffesites.com/en/glossary/ai-agent" rel="noopener noreferrer"&gt;What is an AI agent?&lt;/a&gt;. And for the bigger picture around recurring work: &lt;a href="https://straffesites.com/en/glossary/workflow-automation" rel="noopener noreferrer"&gt;Workflow automation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the change lands: MCP into your CMS, or Git for your Astro site
&lt;/h2&gt;

&lt;p&gt;So far, every improvement went to "your codebase". But where exactly the agent writes depends on how your website is built — and that is less complicated than it sounds. Broadly, there are two routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route one: your site runs on a CMS.&lt;/strong&gt; Then you want the agent to work directly in that CMS, and for that you use MCP: the Model Context Protocol, an open standard that connects AI applications to external systems in a structured way. An MCP server for WordPress or Storyblok offers the agent a limited, explicit list of functions — create a draft, update a field, publish a page — with matching permissions. That way the agent can adjust a page on its own after an approved analysis, but only through the doors you open. And because every CMS draft starts as a draft rather than going live immediately, you keep a natural review step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route two: your site is static, like this one.&lt;/strong&gt; Straffe Sites runs on Astro with Content Collections: every page and article is a file in a git repository, with strict schemas as the quality gate. Here the agent does not edit a CMS but the files themselves — update frontmatter, rewrite an intro, add an internal link — and every change goes straight through the schema and build checks. Each run becomes a small commit you can read, revert, or approve. No database, no admin screen; the repository is the CMS.&lt;/p&gt;

&lt;p&gt;Both routes end in the same place: one small, verified change per run, with a human eye on it before it goes live.&lt;/p&gt;

&lt;p&gt;Further reading by topic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://straffesites.com/en/blog/what-is-a-headless-cms" rel="noopener noreferrer"&gt;What is a headless CMS?&lt;/a&gt; — when managing content separately from the frontend makes sense, and what it means for AI workflows&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://straffesites.com/en/blog/mcp-servers-for-cms" rel="noopener noreferrer"&gt;MCP servers for CMS systems&lt;/a&gt; — permissions, a real draft repair, and readback before publication&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://straffesites.com/en/blog/wordpress-maintenance" rel="noopener noreferrer"&gt;WordPress maintenance&lt;/a&gt; — what periodic maintenance on a CMS site involves, and where an agent can help&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Plan B if the loop does not take off
&lt;/h2&gt;

&lt;p&gt;Sometimes it does not work. The data is too thin (less than a few hundred impressions a month), the market too competitive, or the agent keeps making silly choices after months of course-correcting. Then the honest conclusion is: automation adds no value here.&lt;/p&gt;

&lt;p&gt;But you still win. Because the agent still functions as an &lt;strong&gt;analyst&lt;/strong&gt;: it pulls your data monthly, flags the notable movements, and puts it all in a fixed report. You do the interpretation and the changes yourself, once a quarter. That is not a self-improving loop, but it is a return on your onboarding time — and sometimes that is the realistic picture for a small website. Start small, measure honestly, and let the automation earn what it is worth. And if you would rather build that cycle with guidance than figure it out alone, &lt;a href="https://straffesites.com/en/ai-automation" rel="noopener noreferrer"&gt;AI automation with human control&lt;/a&gt; is where we help. Doubting the business case? &lt;a href="https://straffesites.com/en/blog/seo-costs" rel="noopener noreferrer"&gt;What SEO costs in 2026&lt;/a&gt; lets you compare the agent approach with what freelancers and agencies charge for the same recurring work. Considering an agent beyond SEO, for your whole business? The guide on &lt;a href="https://straffesites.com/en/blog/hiring-someone-to-build-an-ai-agent" rel="noopener noreferrer"&gt;hiring someone to build an AI agent&lt;/a&gt; walks through the signals, costs, and pitfalls.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on &lt;a href="https://straffesites.com/en/blog/automating-seo-with-ai" rel="noopener noreferrer"&gt;Straffe Sites&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>automation</category>
      <category>googlesearchconsole</category>
    </item>
    <item>
      <title>Google Search Console setup: verification and first checks</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Fri, 25 Sep 2026 12:38:15 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/google-search-console-setup-verification-and-first-checks-2cni</link>
      <guid>https://dev.to/seppegadeyne/google-search-console-setup-verification-and-first-checks-2cni</guid>
      <description>&lt;p&gt;A deployed site isn't necessarily an indexed site. Google Search Console lets you check what Google has discovered, which pages it has indexed, and which searches bring people to your site.&lt;/p&gt;

&lt;p&gt;This guide covers adding a property, verifying ownership, and the first checks I'd make after setup. You need a Google account and access to the site's DNS settings, or help from whoever manages them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Search Console tells you
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://search.google.com/search-console/about" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt; is Google's free tool for tracking search performance. It reports impressions and clicks, the queries behind them, indexing status, and technical issues that may affect visibility.&lt;/p&gt;

&lt;p&gt;Google Analytics measures what visitors do on your site. Search Console measures how the site appears in Google Search before they click. They answer different questions. Search Console alone won't tell you whether a visitor submitted a form or became a customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign in with your Google account
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://search.google.com/search-console/" rel="noopener noreferrer"&gt;Search Console&lt;/a&gt; and sign in. Choose an account you'll retain access to. If you're setting this up for a client, make sure they retain ownership rather than leaving access tied only to your account. You can add other users with their own permissions later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add your website: domain or URL prefix
&lt;/h2&gt;

&lt;p&gt;Click "Add property" and choose between two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;domain property&lt;/strong&gt; covers the entire domain, including subdomains and both HTTP and HTTPS. Enter the domain without a protocol or path. This requires DNS verification.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;URL-prefix property&lt;/strong&gt; covers addresses that begin with the exact prefix you enter, such as &lt;code&gt;https://www.example.com/&lt;/code&gt;. Use this when you deliberately want to measure one part or version of the site, or need a verification method other than DNS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a whole website, I'd usually start with a domain property. Be careful with URL prefixes: &lt;code&gt;https://example.com/&lt;/code&gt; and &lt;code&gt;https://www.example.com/&lt;/code&gt; are different prefixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify ownership
&lt;/h2&gt;

&lt;p&gt;For a domain property, copy the TXT value Google provides and add it to the DNS records at your DNS provider. Keep existing records intact. Then return to Search Console and click "Verify."&lt;/p&gt;

&lt;p&gt;DNS changes can take time. If verification fails, check the record name and value against your provider's instructions before trying again. Keep the verification record after setup so Google can recheck ownership.&lt;/p&gt;

&lt;p&gt;A URL-prefix property supports additional methods, including an HTML file or tag, Google Analytics, and Google Tag Manager. Each has its own requirements. &lt;a href="https://support.google.com/webmasters/answer/9008080" rel="noopener noreferrer"&gt;Google's verification documentation&lt;/a&gt; explains those requirements and troubleshooting steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submit your sitemap
&lt;/h2&gt;

&lt;p&gt;Open "Sitemaps" and submit the actual XML sitemap URL generated by your site. Common paths include &lt;code&gt;/sitemap.xml&lt;/code&gt; and &lt;code&gt;/sitemap-index.xml&lt;/code&gt;, but check your deployment rather than guessing.&lt;/p&gt;

&lt;p&gt;Open that URL yourself first. It should return the sitemap, not an HTML error page or a login screen. A sitemap helps Google discover URLs; it doesn't guarantee indexing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect an important page
&lt;/h2&gt;

&lt;p&gt;Paste a complete page URL into the URL Inspection field. Start with your homepage and one important content page.&lt;/p&gt;

&lt;p&gt;Read the indexed result before running a live test. The indexed result describes Google's stored information; a live test checks the current page. A successful live test doesn't mean the URL is already indexed.&lt;/p&gt;

&lt;p&gt;For a new or substantially updated page, you can request indexing through URL Inspection. That is a request, not a guarantee of inclusion or a particular crawl time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the Performance report
&lt;/h2&gt;

&lt;p&gt;Data collection starts when the property is first added to any Search Console account, even before verification. It doesn't recreate search performance from before that first addition.&lt;/p&gt;

&lt;p&gt;Once you have enough data, open the &lt;a href="https://support.google.com/webmasters/answer/7576553" rel="noopener noreferrer"&gt;Performance report&lt;/a&gt; and select "Search results."&lt;/p&gt;

&lt;p&gt;Start in the Pages tab. Click a page with substantial impressions, then switch to Queries to see the searches associated with it. Ask whether the page answers what those people are looking for before changing its title or adding content.&lt;/p&gt;

&lt;p&gt;Average position isn't a fixed ranking. It combines different searches, and results vary by device, location, and other factors. Likewise, low click-through rate doesn't prove that a title is wrong. Compare similar queries and conditions, then inspect the actual search results.&lt;/p&gt;

&lt;p&gt;If you want more context on choosing relevant queries, I've written a guide to &lt;a href="https://straffesites.com/en/blog/how-to-search-for-keywords" rel="noopener noreferrer"&gt;keyword research&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review indexing separately from traffic
&lt;/h2&gt;

&lt;p&gt;The Pages report under Indexing shows which URLs are indexed and which are not, with reasons. "Discovered, currently not indexed" and "Crawled, currently not indexed" can have several causes. Inspect the affected URL and its content before assuming you need a code fix.&lt;/p&gt;

&lt;p&gt;The Sitemaps report shows the status of submitted sitemaps and the URLs Google discovered through them. Those discovery counts aren't a count of indexed pages.&lt;/p&gt;

&lt;p&gt;For ongoing work, keep a record of what you changed and when. Review a meaningful period rather than reacting to a single day's movement. If the goal is more inquiries or sales, track those outcomes separately from search clicks.&lt;/p&gt;




&lt;p&gt;Adapted from my guide on &lt;a href="https://straffesites.com/en/blog/google-search-console" rel="noopener noreferrer"&gt;setting up Google Search Console&lt;/a&gt;, originally published on Straffe Sites.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>beginners</category>
      <category>analytics</category>
    </item>
    <item>
      <title>A small docs MCP server: search, retrieve, and keep track of the source</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Thu, 24 Sep 2026 09:51:13 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/a-small-docs-mcp-server-search-retrieve-and-keep-track-of-the-source-46h</link>
      <guid>https://dev.to/seppegadeyne/a-small-docs-mcp-server-search-retrieve-and-keep-track-of-the-source-46h</guid>
      <description>&lt;p&gt;An agent can remember an API that no longer matches the project in front of it. Giving it documentation helps, but the source matters: the newest documentation and the documentation for an installed version are not necessarily the same.&lt;/p&gt;

&lt;p&gt;I use two small TypeScript servers to make that distinction explicit: &lt;a href="https://github.com/seppegadeyne/tailwind-docs-mcp" rel="noopener noreferrer"&gt;tailwind-docs-mcp&lt;/a&gt; reads Tailwind's documentation source, while &lt;a href="https://github.com/seppegadeyne/hermes-docs-mcp" rel="noopener noreferrer"&gt;hermes-docs-mcp&lt;/a&gt; prefers a local Hermes documentation tree and falls back to GitHub.&lt;/p&gt;

&lt;p&gt;Both expose search, retrieval, and listing over MCP stdio. Neither needs an embedding service or vector database. The interesting engineering is in parsing the documentation, ranking a useful page above an incidental mention, and knowing when the index is stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the agent a small retrieval loop
&lt;/h2&gt;

&lt;p&gt;Each server has three tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Tailwind&lt;/th&gt;
&lt;th&gt;Hermes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Find candidate pages&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search_tailwind_docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search_hermes_docs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read one page&lt;/td&gt;
&lt;td&gt;&lt;code&gt;get_tailwind_doc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;get_hermes_doc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browse available slugs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list_tailwind_docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list_hermes_docs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Search returns titles, slugs, source URLs, and snippets. The agent selects a slug and retrieves the page before answering. Listing helps when it does not know the right terminology; Hermes also supports filtering by section.&lt;/p&gt;

&lt;p&gt;I prefer this to returning an entire manual for every question. Search is a navigation step, though, not a substitute for reading the page. A snippet can omit a warning or the condition that makes an example valid.&lt;/p&gt;

&lt;p&gt;Unknown slugs return an MCP error with suggested matches. Neither server needs a tool that writes into the consuming project's files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailwind: parse the source instead of rendering the website
&lt;/h2&gt;

&lt;p&gt;Tailwind's adapter reads MDX under &lt;code&gt;src/docs&lt;/code&gt; in &lt;code&gt;tailwindlabs/tailwindcss.com&lt;/code&gt;. It extracts exported titles and descriptions, headings, recognizable utility classes, and literal API-table rows. It removes selected MDX wrappers to produce Markdown-like content.&lt;/p&gt;

&lt;p&gt;This is a source-specific parser, not an MDX runtime. It does not execute documentation components. Computed tables and unusual component syntax can lose information, and the cleanup can alter code-like expressions. Use the linked documentation page when exact presentation or a complete generated example matters.&lt;/p&gt;

&lt;p&gt;The search scorer gives title and slug matches more weight than body mentions. It also recognizes utility families: a color utility such as &lt;code&gt;bg-red-500&lt;/code&gt; gets a boost toward &lt;code&gt;background-color&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That domain knowledge matters because many unrelated examples contain background classes. A plain text match can find a real occurrence on the wrong reference page.&lt;/p&gt;

&lt;p&gt;During verification, this call to &lt;code&gt;search_tailwind_docs&lt;/code&gt; ranked &lt;code&gt;background-color&lt;/code&gt; first:&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="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"bg-red-500 background"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&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 retrieve the selected page with &lt;code&gt;get_tailwind_doc&lt;/code&gt;:&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="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"background-color"&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;Those calls were executed against the pinned server. They show one working query, not a search-quality benchmark across the whole documentation set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build both servers from pinned source
&lt;/h2&gt;

&lt;p&gt;You need Git, npm, Node.js 20 or newer, and an MCP client that can launch stdio servers. GitHub access is needed for the default remote sources. A local documentation directory avoids those runtime fetches; Node and the server's npm dependencies are still required.&lt;/p&gt;

&lt;p&gt;These are Bash commands for Linux, macOS, or WSL. Native Windows is not tested here. Start in a directory where you want the checkouts:&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/seppegadeyne/tailwind-docs-mcp.git
&lt;span class="nb"&gt;cd &lt;/span&gt;tailwind-docs-mcp
git checkout &lt;span class="nt"&gt;--detach&lt;/span&gt; 072985944b26721d3c8d8161255fdde97da5f47e
npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
npm &lt;span class="nb"&gt;test
&lt;/span&gt;npm run build
&lt;span class="nb"&gt;cd&lt;/span&gt; ..

git clone https://github.com/seppegadeyne/hermes-docs-mcp.git
&lt;span class="nb"&gt;cd &lt;/span&gt;hermes-docs-mcp
git checkout &lt;span class="nt"&gt;--detach&lt;/span&gt; 0decc76d0d9a4f5b54ffa8b73c4dcc9f5d9c018b
npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
npm &lt;span class="nb"&gt;test
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The locked installs and builds passed during verification on Linux with Node 25.2.1 and npm 11.6.2. Tailwind's three tests and Hermes's 15 tests passed. These suites check parser and search behavior using fixtures; the separate MCP smoke tests exercised discovery and real remote retrieval.&lt;/p&gt;

&lt;p&gt;If you have not installed Hermes yet, my guide on Voltti walks through &lt;a href="https://voltti.ai/en/insights/install-hermes-desktop-app-or-terminal" rel="noopener noreferrer"&gt;installing Hermes with the desktop app or terminal&lt;/a&gt;. Return here once your client is ready; the configuration below connects it to the documentation servers.&lt;/p&gt;

&lt;p&gt;For an existing Hermes client, merge the following into its configuration, using your own absolute paths. The syntax follows the &lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp" rel="noopener noreferrer"&gt;official MCP guide&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mcp_servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tailwind-docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/absolute/path/to/tailwind-docs-mcp/dist/cli.js"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;180&lt;/span&gt;
    &lt;span class="na"&gt;connect_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;hermes-docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/absolute/path/to/hermes-docs-mcp/dist/cli.js"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;180&lt;/span&gt;
    &lt;span class="na"&gt;connect_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check discovery before asking a model to use the tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes mcp &lt;span class="nb"&gt;test &lt;/span&gt;tailwind-docs
hermes mcp &lt;span class="nb"&gt;test &lt;/span&gt;hermes-docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discovery alone does not verify indexing: loading happens when a documentation tool is called. Follow it with the search and retrieval examples. Other clients need their own configuration format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hermes: a local-first source with a remote fallback
&lt;/h2&gt;

&lt;p&gt;Hermes's adapter checks &lt;code&gt;HERMES_DOCS_DIR&lt;/code&gt;, then its conventional local checkout location, and otherwise reads &lt;code&gt;website/docs&lt;/code&gt; from &lt;code&gt;NousResearch/hermes-agent&lt;/code&gt; on GitHub. Unlike the Tailwind local loader, the Hermes loader walks nested directories and accepts both Markdown and MDX.&lt;/p&gt;

&lt;p&gt;It reads simple frontmatter metadata, derives slugs from paths, preserves fenced code, and converts Docusaurus admonitions into text markers. Its parser does not implement every frontmatter feature; a custom route declared in metadata is not necessarily the path-derived URL it returns.&lt;/p&gt;

&lt;p&gt;A local tree can correspond to the installed software, provided you point it at that installation and account for local modifications. The GitHub fallback follows &lt;code&gt;main&lt;/code&gt;. It does not promise that the returned docs describe your installed release.&lt;/p&gt;

&lt;p&gt;The fallback was exercised with an empty home directory and no local override. Search, retrieval, and listing succeeded. The actual list response began:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hermes Agent docs: 3 of 440 pages (installed commit unknown).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;unknown&lt;/code&gt; matters. The current commit helper inspects a local Git checkout; it does not attach a resolved remote SHA to fallback content. The wording says “installed commit” even on the remote path. Do not interpret it as verified version provenance.&lt;/p&gt;

&lt;p&gt;For a known page, this is the argument to &lt;code&gt;get_hermes_doc&lt;/code&gt;:&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="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"user-guide/features/mcp"&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;The &lt;a href="https://glama.ai/mcp/servers/seppegadeyne/hermes-docs-mcp" rel="noopener noreferrer"&gt;Glama listing&lt;/a&gt; provides discovery, not evidence of a successful hosted deployment. The standalone test establishes that the source loader works without a local Hermes checkout. Hosting still needs the runtime, a transport arrangement, and network access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freshness and reproducibility need separate decisions
&lt;/h2&gt;

&lt;p&gt;Both remote loaders cache their index in process memory without a time-based refresh. Restarting the process rebuilds it on the next request. Hermes caches its local index for 60 seconds; Tailwind rereads its local directory on each load.&lt;/p&gt;

&lt;p&gt;For reproducible documentation, check out the upstream doc repository at a chosen commit and point the server at it. These are illustrative environment-variable examples; replace the paths with actual documentation directories and start the command from the corresponding server checkout:&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;TAILWIND_DOCS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/absolute/path/to/tailwindcss.com/src/docs npm start
&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;HERMES_DOCS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/absolute/path/to/hermes-agent/website/docs npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set those variables in the client's server environment for normal MCP use. An explicitly configured but invalid Hermes directory fails locally rather than silently selecting GitHub.&lt;/p&gt;

&lt;p&gt;Pinning the MCP adapter does not pin remotely fetched docs. The current loaders request the tree and individual files through moving &lt;code&gt;main&lt;/code&gt; references, so an upstream change during indexing could mix revisions. They also fetch pages with &lt;code&gt;Promise.all&lt;/code&gt;; they do not implement bounded concurrency or a persistent disk cache. A network failure can prevent the index from loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would carry into another documentation adapter
&lt;/h2&gt;

&lt;p&gt;Start with a normalized page record and tests for your source format. Preserve its URL, source path, and code examples. Build a small query set with expected reference pages, including terms that occur in many examples. The live Hermes query &lt;code&gt;mcp_servers&lt;/code&gt;, for example, ranked a plugin guide first during verification. Lexical scoring is understandable, but it still needs evaluation.&lt;/p&gt;

&lt;p&gt;Before distributing a corpus, check the documentation's own license and attribution requirements. Public source access is not a blanket redistribution license. Treat retrieved text as reference material, not instructions that grant the agent new permissions.&lt;/p&gt;

&lt;p&gt;For a production adaptation, I would resolve one source SHA before fetching, report it with results, bound fetch concurrency, and define an explicit refresh policy. Those are improvements to make, not features to assume these versions already have.&lt;/p&gt;

&lt;p&gt;The small tool surface is reusable. The parser, ranking rules, and version policy are the parts that need to fit the documentation you are serving.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>typescript</category>
      <category>documentation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Giving an AI agent a spell checker with MCP</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Thu, 24 Sep 2026 09:33:48 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/giving-an-ai-agent-a-spell-checker-with-mcp-a0h</link>
      <guid>https://dev.to/seppegadeyne/giving-an-ai-agent-a-spell-checker-with-mcp-a0h</guid>
      <description>&lt;p&gt;A writing agent can produce a fluent paragraph and still choose the wrong spelling convention. A word can pass a dictionary check and still conflict with a style guide. US English makes a useful example: spelling and regional usage are separate checks.&lt;/p&gt;

&lt;p&gt;I keep those decisions outside the model in &lt;a href="https://github.com/seppegadeyne/language-mcp" rel="noopener noreferrer"&gt;language-mcp&lt;/a&gt;: a small TypeScript MCP server with local spelling and simplicity checks. It currently supports US English and Dutch, with a separate Dutch word lookup and a B1 simplicity check for each language. The examples below use US English and Dutch; the same design can be extended to other languages with suitable dictionaries, rules, and tools. The agent submits text, reads the findings, and decides which changes make sense. The server does not rewrite files.&lt;/p&gt;

&lt;p&gt;The useful part of this project is the boundary between a dictionary, a style rule, and an editorial decision. They answer different questions, even when all three produce something that looks like a correction.&lt;/p&gt;

&lt;h2&gt;
  
  
  A local dictionary first, a network lookup when needed
&lt;/h2&gt;

&lt;p&gt;Six tools use the Hunspell executable with bundled dictionaries. Dutch uses OpenTaal; US English uses the SCOWL/LibreOffice dictionary assets. Spelling checks do not send the submitted text to a spelling API.&lt;/p&gt;

&lt;p&gt;That privacy statement applies to this server, not to the whole agent session. If your MCP client uses a hosted language model, its conversation and tool results may still reach that provider. I explain this distinction in more detail in our guide to &lt;a href="https://voltti.ai/en/insights/what-stays-local-in-a-hybrid-ai-setup" rel="noopener noreferrer"&gt;what stays local in a hybrid AI setup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The one network tool, &lt;code&gt;get_dutch_word_details&lt;/code&gt;, queries &lt;a href="https://www.woordenlijst.org" rel="noopener noreferrer"&gt;Woordenlijst.org&lt;/a&gt;, maintained by the Instituut voor de Nederlandse Taal for the Taalunie. It provides lexical details such as pronunciation, word forms, and hyphenation. This is a separate network operation, useful for an individual word that needs investigation.&lt;/p&gt;

&lt;p&gt;The Dutch dictionary source is &lt;a href="https://github.com/OpenTaal/opentaal-hunspell" rel="noopener noreferrer"&gt;OpenTaal's Hunspell repository&lt;/a&gt;. Keep the dictionary attribution and license files when redistributing the assets; a server's code license does not replace them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I kept the Hunspell executable
&lt;/h2&gt;

&lt;p&gt;The project's &lt;a href="https://github.com/seppegadeyne/language-mcp/blob/e769c5d960c92a4455f1be0607cc68b873dffab2/docs/adr/001-hunspell-binary-over-nspell.md" rel="noopener noreferrer"&gt;architecture decision record&lt;/a&gt; considers replacing the executable with &lt;code&gt;nspell&lt;/code&gt;, a JavaScript implementation. Removing a native dependency would simplify deployment. I kept the executable because changing the implementation also means proving behavioral compatibility.&lt;/p&gt;

&lt;p&gt;A particularly easy mistake sits in Hunspell's &lt;code&gt;-a&lt;/code&gt; pipe protocol. It does not promise one response line for every submitted token. A hyphenated input can produce a response for each part, followed by an empty separator line.&lt;/p&gt;

&lt;p&gt;If the wrapper zips individual response lines with input words, one compound can shift the remaining results onto the wrong words. The current implementation groups output into blocks separated by empty lines, then associates each block with one unique input token. It rejects a response-count mismatch instead of returning misaligned findings.&lt;/p&gt;

&lt;p&gt;Within a block, the wrapper accepts &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;+&lt;/code&gt; responses, collects suggestions from &lt;code&gt;&amp;amp;&lt;/code&gt; responses, and treats unknown responses as failures. It removes duplicate suggestions and returns at most eight per token. It also limits the subprocess to 20 seconds.&lt;/p&gt;

&lt;p&gt;That explains the dependency better than a speed claim. The ADR discusses performance, but I am not presenting a Hunspell-versus-nspell benchmark here. A replacement should pass the same compound and suggestion tests before deployment gets simpler at the expense of correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the pinned version
&lt;/h2&gt;

&lt;p&gt;You need Git, Node.js 20 or newer, npm, and &lt;code&gt;hunspell&lt;/code&gt; available on the MCP host's &lt;code&gt;PATH&lt;/code&gt;. The Bash commands below target Linux, macOS, or WSL. Native Windows setup is not covered here; with WSL, install the dependencies and run the client inside that environment.&lt;/p&gt;

&lt;p&gt;Install Hunspell through your operating system's package manager first. The repository README gives &lt;code&gt;pacman -S hunspell&lt;/code&gt; for Arch Linux. Package installation may require administrator privileges. Check the actual executable before building:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
npm &lt;span class="nt"&gt;--version&lt;/span&gt;
hunspell &lt;span class="nt"&gt;--version&lt;/span&gt;

git clone https://github.com/seppegadeyne/language-mcp.git
&lt;span class="nb"&gt;cd &lt;/span&gt;language-mcp
git checkout &lt;span class="nt"&gt;--detach&lt;/span&gt; e769c5d960c92a4455f1be0607cc68b873dffab2
npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
npm &lt;span class="nb"&gt;test
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The checkout pins the source discussed here rather than silently following future changes. Run the locked dependency install, tests, and build on your MCP host before configuring your client. A successful run on one machine is not proof that every Node version or operating system has been exercised.&lt;/p&gt;

&lt;p&gt;In an existing &lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp" rel="noopener noreferrer"&gt;Hermes installation&lt;/a&gt;, merge this entry into &lt;code&gt;mcp_servers&lt;/code&gt; in your configuration. Replace the example path with your checkout's absolute path; do not replace unrelated server entries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mcp_servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/absolute/path/to/language-mcp/dist/cli.js"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;connect_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test discovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes mcp &lt;span class="nb"&gt;test &lt;/span&gt;language
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server speaks MCP over stdio, so &lt;code&gt;node dist/cli.js&lt;/code&gt; is not an interactive spelling prompt. Your client launches it and exchanges protocol messages. Other stdio-capable MCP clients need their own configuration syntax, and their tool names may include a server prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the US English tools
&lt;/h2&gt;

&lt;p&gt;These JSON objects are tool arguments, not shell commands. Select the named tool through your MCP client or ask your agent to call it.&lt;/p&gt;

&lt;p&gt;For a paragraph, use &lt;code&gt;check_us_english_text&lt;/code&gt;:&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="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"While moving toward the center, we organized everything."&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;The verified response is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US English check (hunspell en_US + britticism scan)
8 words checked, 0 unknown, 0 British forms.
OK: no typos, no British forms found.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For one word, use &lt;code&gt;validate_us_english_word&lt;/code&gt;:&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="nl"&gt;"word"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"color"&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;The verified response is &lt;code&gt;"color" is correct US English.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To see the regional style check in action, replace &lt;code&gt;While&lt;/code&gt; with the intentionally British &lt;code&gt;Whilst&lt;/code&gt;, and &lt;code&gt;toward&lt;/code&gt; with &lt;code&gt;towards&lt;/code&gt;. These are test inputs, not recommended US English. The dictionary accepts them, but the separate style rules flag both and suggest the US forms. A dictionary check alone would miss that distinction.&lt;/p&gt;

&lt;p&gt;The server also provides &lt;code&gt;check_dutch_text&lt;/code&gt;, &lt;code&gt;validate_dutch_word&lt;/code&gt;, and &lt;code&gt;get_dutch_word_details&lt;/code&gt;. The first two use the local Dutch dictionary; the last uses the network for lexical details. You do not need the network lookup for the US English examples. Two simplicity checks, &lt;code&gt;check_dutch_b1_text&lt;/code&gt; and &lt;code&gt;check_us_english_b1_text&lt;/code&gt;, are covered next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking Dutch text for B1-level simplicity
&lt;/h2&gt;

&lt;p&gt;Version 0.4.0 adds &lt;code&gt;check_dutch_b1_text&lt;/code&gt;, for a question the spelling tools cannot answer: is this Dutch text simple enough for a broad audience?&lt;/p&gt;

&lt;p&gt;"Dutch at B1" is an editorial convention, not a validated text measurement. The Rijksoverheid communication guideline asks writers to aim for B1 (CEFR) in new public-facing material. A formula cannot certify that a text meets it, so the tool reports proxies a reviewer can check in context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Readability: Flesch-Douma reading ease (the Dutch-adapted Flesch formula) and ARI, with a practical B1 band around 60-70 for Flesch-Douma. Below 50 words or three sentences, the formulas are suppressed instead of reporting noise.&lt;/li&gt;
&lt;li&gt;Sentence and paragraph length against configurable thresholds (defaults: warn above 15 words per sentence, flag above 20; 150 words per paragraph).&lt;/li&gt;
&lt;li&gt;Passive voice, using a second Hunspell pass described below.&lt;/li&gt;
&lt;li&gt;Officialese jargon with plain alternatives (&lt;code&gt;thans&lt;/code&gt; to &lt;code&gt;nu&lt;/code&gt;, &lt;code&gt;betreffende&lt;/code&gt; to &lt;code&gt;over&lt;/code&gt;), filler words, and idioms that are hard for NT2 readers, each with its position.&lt;/li&gt;
&lt;li&gt;Nominalization density (&lt;code&gt;-ing&lt;/code&gt;/&lt;code&gt;-tie&lt;/code&gt;/&lt;code&gt;-heid&lt;/code&gt;/&lt;code&gt;-iteit&lt;/code&gt; words per 100 words) and je/u voice consistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The passive detector is a second Hunspell protocol story. The &lt;code&gt;-a&lt;/code&gt; pipe from the spelling tools returns no morphology, but &lt;code&gt;hunspell -m&lt;/code&gt; returns &lt;code&gt;st:&lt;/code&gt; stem and &lt;code&gt;ts:&lt;/code&gt; tag fields from the OpenTaal affix file. Three quirks matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only affix-derived forms get the past-participle tag &lt;code&gt;ts:VBpe&lt;/code&gt; (&lt;code&gt;gemaakte&lt;/code&gt;); bare participles such as &lt;code&gt;verzonden&lt;/code&gt; are their own lemma without a tag.&lt;/li&gt;
&lt;li&gt;Separable compounds analyze as compound stems: &lt;code&gt;uitgevoerd&lt;/code&gt; becomes &lt;code&gt;uit st:gevoerd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Tags are context-free: &lt;code&gt;is&lt;/code&gt; plus a participle can be a perfect tense or a passive, so those hits are labeled low confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wrapper combines the tag, a bare-lemma check with Dutch participle phonology, and a compound-stem rule, then scans for auxiliaries (&lt;code&gt;wordt&lt;/code&gt;, &lt;code&gt;worden&lt;/code&gt;, &lt;code&gt;werd&lt;/code&gt;, &lt;code&gt;is&lt;/code&gt;, &lt;code&gt;zijn&lt;/code&gt;) followed by a participle. Every hit is a review flag with its sentence and position, not a verdict.&lt;/p&gt;

&lt;p&gt;An intentionally bureaucratic example:&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="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Wellicht wordt de aanvraag thans door de gemeente in behandeling genomen. De implementatie van de regeling betreffende de vergoeding wordt vervolgens door de afdeling uitgevoerd, wat betekent dat u als klant langer moet wachten op een beslissing dan u wellicht zou verwachten op basis van de eerder door ons gedane toezeggingen. Je krijgt daarna een bericht. Onder de loep genomen?"&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;The verified response, abbreviated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dutch B1 simplicity check (B1 proxies, not a validated B1 verdict)

Readability:
- Flesch-Douma: 54 (band: below-b1; B1 target 60-70)
- ARI: 11.4 (grade-level indication)
- Words: 58, sentences: 4, avg 14.5 words/sentence
- Sentences over 15 words: 1 (1 over 20)
- Long words (&amp;gt;=4 syllables): 7%

Passive voice (review flags, not verdicts):
- "wordt genomen" (+ door/van agent) — "Wellicht wordt de aanvraag thans door de gemeente..." (position 0)
- "wordt uitgevoerd" (+ door/van agent) — "De implementatie van de regeling betreffende..." (position 73)

Jargon and vague wording (plain alternatives):
- "thans" → nu (position 27)
- "implementatie" → uitvoering, uitrollen (position 77)
- "betreffende" → over, bij (position 107)

Filler words:
- "Wellicht" → misschien (position 0)

Idioms (consider plain phrasing for NT2 readers):
- "Onder de loep genomen" → goed bekijken (position 359)

Nominalization density: 10.3 per 100 words (high — prefer verbs over nouns)

Voice consistency: MIXED (expected je): u-forms 2x at position 196, je-forms 1x at position 329 — pick one address form.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The same check for US English
&lt;/h2&gt;

&lt;p&gt;Version 0.5.0 adds &lt;code&gt;check_us_english_b1_text&lt;/code&gt;. It asks the same question about US English copy. The output has the same structure: readability, passive voice, rule findings with plain alternatives and positions, nominalization density, and reader address. "B1" is still shorthand for plain language, not a certified level.&lt;/p&gt;

&lt;p&gt;The English version needs its own measurements, not translated Dutch rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Readability: Flesch Reading Ease (the original English formula, with 60-70 as the practical plain-English band), Flesch-Kincaid grade level (default target 9 or lower), and ARI. A zero-dependency syllable counter feeds the formulas. It agrees with the CMU Pronouncing Dictionary on about 94% of the 10,000 most common US English words. That is accurate enough for averages over a text, not for individual words.&lt;/li&gt;
&lt;li&gt;Sentence length: warn above 20 words and flag above 25, following plainlanguage.gov and the GOV.UK style guide. Both thresholds are tool parameters.&lt;/li&gt;
&lt;li&gt;Rules from the Federal Plain Language Guidelines and the GOV.UK "words to avoid" list. They cover formal jargon (&lt;code&gt;utilize&lt;/code&gt; to &lt;code&gt;use&lt;/code&gt;, &lt;code&gt;prior to&lt;/code&gt; to &lt;code&gt;before&lt;/code&gt;), wordy phrases (&lt;code&gt;in order to&lt;/code&gt; to &lt;code&gt;to&lt;/code&gt;), hidden verbs (&lt;code&gt;make a decision&lt;/code&gt; to &lt;code&gt;decide&lt;/code&gt;), filler words, and idioms. British spellings stay in &lt;code&gt;check_us_english_text&lt;/code&gt;, so nothing is reported twice.&lt;/li&gt;
&lt;li&gt;Reader address: the tool lists references to "users" or "the customer" when the text could address the reader as "you".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Passive detection was the second Hunspell surprise. The bundled en_US affix file has no &lt;code&gt;ts:&lt;/code&gt; tags at all. &lt;code&gt;hunspell -m&lt;/code&gt; marks regular -ed forms with the affix flag &lt;code&gt;fl:D&lt;/code&gt; (&lt;code&gt;created  st:create fl:D&lt;/code&gt;), while irregular participles such as &lt;code&gt;written&lt;/code&gt; or &lt;code&gt;built&lt;/code&gt; come back as bare lemmas. The detector therefore combines the flag with a closed list of irregular participles. It also has to skip hyphenated words, because the en_US dictionary splits &lt;code&gt;data-driven&lt;/code&gt; into one morphology block per part. Participles that usually describe a state after "be" (&lt;code&gt;is located&lt;/code&gt;, &lt;code&gt;are required&lt;/code&gt;) are labeled low confidence.&lt;/p&gt;

&lt;p&gt;An intentionally bureaucratic example:&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="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"In order to facilitate the processing of your request, it is essential that applicants utilize the online portal prior to the deadline. Requests that are submitted after the deadline will not be considered by the committee. Users are required to make a payment before a decision can be reached. Due to the fact that the system is undergoing maintenance, additional delays may be experienced. You will get an email when we are done."&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;The verified response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;US English B1 plain-language check (B1 proxies, not a validated B1 verdict)

Readability:
- Flesch Reading Ease: 51.8 (band: below-b1; B1 target 60-70)
- Flesch-Kincaid grade: 9.6 (above target 9)
- ARI: 8.9 (grade-level indication)
- Words: 71, sentences: 5, avg 14.2 words/sentence
- Sentences over 20 words: 1 (0 over 25)
- Complex words (&amp;gt;=3 syllables): 20%

Passive voice (review flags, not verdicts):
- "are submitted" — "Requests that are submitted after the deadline will not be considered by the committee." (position 135)
- "be considered" (+ by agent) — "Requests that are submitted after the deadline will not be considered by the committee." (position 135)
- "are required" [adjectival use likely, low confidence] — "Users are required to make a payment before a decision can be reached." (position 223)
- "be reached" — "Users are required to make a payment before a decision can be reached." (position 223)
- "be experienced" [adjectival use likely, low confidence] — "Due to the fact that the system is undergoing maintenance, additional delays may be exper…" (position 294)
- "are done" [adjectival use likely, low confidence] — "You will get an email when we are done." (position 391)

Jargon and buzzwords (plain alternatives):
- "facilitate" → help, make possible (position 12)
- "utilize" → use (position 87)
- "prior to" → before (position 113)
- "required" → need, must (position 234) — common in web copy; judge in context
- "additional" → more, extra (position 354)

Wordy phrases:
- "In order to" → to (position 0)
- "it is essential that" → (state the point directly) (position 55)
- "Due to the fact that" → because (position 295)

Hidden verbs (use the verb, not the noun):
- "make a payment" → pay (position 246)

Nominalization density: 2.8 per 100 words
  Top: decision (1x), maintenance (1x)

Reader address: third-person references to the reader 2x (first at position 76): applicants (1x), users (1x) — address the reader as "you" where they are the audience; "you" used 2x.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last hit shows why these remain review flags: "when we are done" is an adjective, not a passive, and the tool labels it low confidence instead of hiding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extend the pattern to another language
&lt;/h2&gt;

&lt;p&gt;MCP does not tie this design to English or Dutch. The current server implements those two languages; adding another one requires code and dictionary assets, not just a language name in a request.&lt;/p&gt;

&lt;p&gt;Start with a compatible Hunspell dictionary from a trustworthy source. Include its dictionary and affix files, preserve its license and attribution, and add it to the dictionary resolver. Then register language-specific text-checking and word-validation tools with the same result structure.&lt;/p&gt;

&lt;p&gt;Add regional or editorial rules only when the dictionary cannot express the distinction you need. A lexical lookup is optional and requires a suitable source for that language; the Dutch lookup is not a universal dictionary API. Finally, test valid words, misspellings, compounds, suggestions, and any language-specific rules before exposing the new tools to an agent.&lt;/p&gt;

&lt;p&gt;The reusable idea is the separation of responsibilities: the dictionary checks spelling, explicit rules check selected conventions, and the agent reviews the findings in context. Language coverage depends on the available dictionaries and the integration work, not on MCP itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat the style rules as review suggestions
&lt;/h2&gt;

&lt;p&gt;The britticism detector is a static list of regular expressions and suggested replacements. Some rules cover spelling variants. Others encode editorial preferences, such as using &lt;code&gt;custom&lt;/code&gt; rather than &lt;code&gt;bespoke&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I would not automatically apply every match. The list also includes context-sensitive vocabulary: &lt;code&gt;queue&lt;/code&gt; is perfectly ordinary in an American programming article, and &lt;code&gt;lift&lt;/code&gt; does not always mean an elevator. Rules can flag valid text or generate an unsuitable inflection. This implementation is not a grammar parser.&lt;/p&gt;

&lt;p&gt;There is no measured coverage percentage behind the rule list. It is inspectable and testable, which makes it useful for enforcing a specific style contract, but it cannot certify that a paragraph is idiomatic US English.&lt;/p&gt;

&lt;p&gt;The two checking paths also treat Markdown differently. The spelling tokenizer removes URLs, email addresses, fenced code, and inline code. The britticism scan receives the original text. A quoted example or identifier can therefore appear in style findings even when spelling ignores it. Preserve source quotations and code; review the surrounding prose instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits belong in the workflow
&lt;/h2&gt;

&lt;p&gt;The tokenizer stops after 2,000 checkable tokens, skips one-character tokens, and skips short all-uppercase acronyms. For a longer document, split it into sections and keep track of what you submitted. A clean result is not evidence that every character in the original document was checked.&lt;/p&gt;

&lt;p&gt;Reported positions are JavaScript string indexes, not editor-ready line and column locations. Since version 0.5.0, each spelling finding reports the offset of the token itself. Earlier versions searched for the first occurrence of the word, which could point inside a longer word (&lt;code&gt;red&lt;/code&gt; inside &lt;code&gt;hundred&lt;/code&gt;). Treat the positions as navigation aids: excluded material and later edits still make automatic patching risky.&lt;/p&gt;

&lt;p&gt;The network lookup has its own boundaries. It uses an unofficial XML service, serializes calls with a minimum 1.2-second interval, and caches results in memory for 24 hours. This is not a bulk dictionary download API or an availability guarantee. Its parser also takes the first matching descendant fields; in the verified &lt;code&gt;pizza&lt;/code&gt; lookup, the top-level hyphenation reflected a diminutive. Check ambiguous lexical details on the source website rather than treating every rendered field as authoritative.&lt;/p&gt;

&lt;p&gt;My preferred workflow is simple: submit a section, inspect the findings, make deliberate edits, and check the edited section again. Keep proper nouns and technical identifiers unless there is a specific reason to change them. Read the whole paragraph afterward. A dictionary cannot tell whether a sentence says what you meant.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://glama.ai/mcp/servers/seppegadeyne/language-mcp" rel="noopener noreferrer"&gt;Glama listing&lt;/a&gt; is another way to discover the server; the pinned repository is the reference for the tool names and behavior described here. A directory listing does not remove the Hunspell runtime requirement.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>typescript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Keyword Research with AI Agents and Google Search Console</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Tue, 22 Sep 2026 09:40:19 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/keyword-research-with-ai-agents-and-google-search-console-4nl7</link>
      <guid>https://dev.to/seppegadeyne/keyword-research-with-ai-agents-and-google-search-console-4nl7</guid>
      <description>&lt;h1&gt;
  
  
  Keyword Research with AI Agents and Google Search Console
&lt;/h1&gt;

&lt;p&gt;Manual keyword research is slow and disconnected from real user behavior. I use &lt;a href="https://voltti.ai/en" rel="noopener noreferrer"&gt;AI agents&lt;/a&gt; connected to Google Search Console data to automate keyword clustering, intent analysis, and content gap detection for client websites.&lt;/p&gt;

&lt;p&gt;This article shows the complete workflow: from raw GSC data to actionable keyword clusters that drive content decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with manual keyword research
&lt;/h2&gt;

&lt;p&gt;Traditional keyword research relies on guesswork. You look at competitor sites, use keyword tools with volume estimates, and try to predict what users might search for. But if you already have a website, Google is telling you exactly what people search for — and you're not listening.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://search.google.com/search-console/about" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt; provides actual search query data, grouped by page, with impressions, clicks, and average position. This is the real signal. The problem is extracting actionable insights from thousands of rows of query data.&lt;/p&gt;

&lt;p&gt;That's where AI agents come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The system consists of three components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data source&lt;/strong&gt;: Google Search Console &lt;a href="https://developers.google.com/webmaster-tools/v1/searchanalytics/query" rel="noopener noreferrer"&gt;Search Analytics API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agent&lt;/strong&gt;: A coding agent with access to the GSC data and the ability to execute scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster output&lt;/strong&gt;: A JSON mapping of queries to pages, intents, and optimization opportunities&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent runs a Node.js script that authenticates with Google Cloud, fetches 12 months of search query data, and processes it into keyword clusters organized by page and intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting up GSC access
&lt;/h2&gt;

&lt;p&gt;Before anything else, you need API access to Search Console data. This requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud project&lt;/a&gt; with the Search Console API enabled&lt;/li&gt;
&lt;li&gt;Application Default Credentials (ADC) configured with &lt;code&gt;gcloud auth application-default login&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The Search Console property added to your account
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure ADC credentials&lt;/span&gt;
gcloud auth application-default login

&lt;span class="c"&gt;# Set the quota project (required since 2026)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_QUOTA_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-project-id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The keyword cluster script
&lt;/h2&gt;

&lt;p&gt;The core of this workflow is a Node.js script that fetches GSC data and processes it. Here's how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Authenticate with GSC API&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mintAdcToken&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gcloud&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application-default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;print-access-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch query data for a specific period&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchGscRows&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endDate&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;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;mintAdcToken&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://searchconsole.googleapis.com/webmasters/v3/sites/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sc-domain:your-site.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/searchAnalytics/query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-goog-user-project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLOUD_QUOTA_PROJECT&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;dataState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;final&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25000&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;rows&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 script fetches up to 25,000 rows of query data spanning the last 12 months. It uses pagination to handle larger datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Processing the data into clusters
&lt;/h2&gt;

&lt;p&gt;Once the data is fetched, the processing happens in several stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Map routes to pages:&lt;/strong&gt; Each query result is mapped to a canonical route, stripping fragments and query parameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aggregate by page:&lt;/strong&gt; For each page, all queries are collected along with their impressions, clicks, and positions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identify intent clusters:&lt;/strong&gt; Queries are grouped by intent. "What is X" queries are informative. "Buy X" or "hire X" queries are commercial. The intent is inferred from both the query phrasing and the page it leads to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detect cannibalization:&lt;/strong&gt; When multiple pages rank for similar queries, the script flags potential cannibalization where pages compete for the same search terms.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of the clustering logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clusterQueries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pageMetrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculatePageMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;primaryTerm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Highest click volume&lt;/span&gt;
    &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;impressions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;impressions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ctr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clicks&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;impressions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long-tail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;sharedWith&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;detectCannibalization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;allPages&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Using the clusters for content decisions
&lt;/h2&gt;

&lt;p&gt;The output is a JSON file that maps each page to its keyword clusters. This becomes the foundation for all content and SEO decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimization targets:&lt;/strong&gt; Pages with high impressions but low clicks are prime optimization targets. The title, meta description, or content may need improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal linking strategy:&lt;/strong&gt; Use the cluster data to identify which pages should link to which. If a page ranks for "website development services" and another ranks for "hire a web developer," they should link to each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content gap identification:&lt;/strong&gt; With &lt;code&gt;query&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; as dimensions, every returned row already pairs a query with a page. A gap here means the site may lack content that fits the search intent, even though an existing page earns impressions for that query. Review the current page and search results before deciding whether to improve it or create a separate page. This export cannot reveal demand for queries where the site has no recorded visibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commercial vs. informational intent:&lt;/strong&gt; The intent labels help decide whether a page should be a blog post (informative) or a landing page (commercial).&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: real data from our site
&lt;/h2&gt;

&lt;p&gt;Here's a real example from our Search Console data (August 2026):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;Current page&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;website laten maken&lt;/td&gt;
&lt;td&gt;2,019&lt;/td&gt;
&lt;td&gt;16.7&lt;/td&gt;
&lt;td&gt;/blog/wat-kost-een-website-laten-maken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;goedkope website laten maken&lt;/td&gt;
&lt;td&gt;1,232&lt;/td&gt;
&lt;td&gt;21.3&lt;/td&gt;
&lt;td&gt;/blog/goedkoop-website-laten-maken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;een website laten maken&lt;/td&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;19.2&lt;/td&gt;
&lt;td&gt;/blog/wat-kost-een-website-laten-maken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;astro website laten maken&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;7.0&lt;/td&gt;
&lt;td&gt;/astro-website-laten-maken&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(These queries are in Dutch — they come from a Dutch-language client site we manage. The workflow itself works the same in any language.)&lt;/p&gt;

&lt;p&gt;What should we investigate?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The price blog earns impressions for "website laten maken." It may answer cost questions well, while a service page may better serve someone ready to hire a developer. The query alone does not establish which intent dominates.&lt;/li&gt;
&lt;li&gt;The row for "goedkope website laten maken" shows one blog. To investigate cannibalization, inspect the other query-page pairs; this table alone does not demonstrate competing pages.&lt;/li&gt;
&lt;li&gt;"astro website laten maken" appears with a relevant service page and an average position of 7.0. That is useful visibility data, not evidence of inquiries or sales.&lt;/li&gt;
&lt;li&gt;Before proposing a new service page for "website laten maken," check whether a suitable one already exists and review the search results. Improving an existing page and its internal links may be the better option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script surfaces candidates for review. It does not establish that a new page is necessary or that it will bring in more business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the workflow with AI agents
&lt;/h2&gt;

&lt;p&gt;The real power comes from automating this workflow. With an AI coding agent, you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Schedule regular runs&lt;/strong&gt;: Run the script weekly or monthly to track changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate reports&lt;/strong&gt;: Have the agent analyze the data and generate optimization recommendations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement changes&lt;/strong&gt;: Have the agent actually update titles, meta descriptions, and internal links based on the cluster data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track results&lt;/strong&gt;: Compare search performance and qualified inquiries before and after changes, using conversion tracking or CRM data alongside GSC&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent doesn't replace your SEO judgment — it amplifies it. You still decide the strategy, but the agent handles the data analysis and implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure inquiries as well as search visibility
&lt;/h2&gt;

&lt;p&gt;For a service business, a new page should help generate relevant inquiries. GSC reports search performance; it does not tell you whether a visitor submitted a useful project inquiry or became a customer. Use analytics conversion tracking for successful form submissions and, where available, CRM records to assess lead quality.&lt;/p&gt;

&lt;p&gt;Before making changes, record organic traffic and qualified inquiries for the relevant existing pages. After launch, compare the combined results for those pages and the new service page, not just the new page in isolation. Otherwise, moving inquiries from the blog to the service page can look like growth when the total has not changed.&lt;/p&gt;

&lt;p&gt;Use comparable, non-overlapping periods of at least 30 days and allow longer when inquiry counts are low. Consider seasonality and other site or marketing changes; a before-and-after comparison alone does not prove causation. A higher ranking without more relevant inquiries is not enough to call the change a business success. These are proposed measurement criteria, not conversion results demonstrated by the table above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always compare fixed periods&lt;/strong&gt;: When measuring changes, use complete, non-overlapping periods. Don't compare this week with last week if they have different numbers of days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track baseline metrics&lt;/strong&gt;: Before making any SEO changes, record the current impressions, clicks, and positions for the affected pages and queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give changes time&lt;/strong&gt;: Reassess after at least 30 days, and wait longer when the data is sparse. Avoid repeated changes before you have enough evidence to judge the previous one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the API, not the UI&lt;/strong&gt;: The Search Analytics API gives you more data and more control than the web interface. It's worth the extra setup.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Keyword research doesn't have to be manual guesswork. By connecting AI agents to your real Google Search Console data, you can build a data-driven SEO workflow that continuously optimizes based on actual user behavior.&lt;/p&gt;

&lt;p&gt;The key insight: you already have the data you need. You just need the right tools to process it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This workflow is based on our experience running keyword research for client websites. The scripts described here are used in production on straffesites.com. New to keyword research? Start with &lt;a href="https://straffesites.com/en/blog/how-to-search-for-keywords" rel="noopener noreferrer"&gt;How to do keyword research in 6 clear steps&lt;/a&gt; on the Straffe Sites blog.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>google</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Hermes Agent for SEO: from Search Console to a blog post</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Mon, 14 Sep 2026 07:28:10 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/hermes-agent-for-seo-from-search-console-to-a-blog-post-1ap0</link>
      <guid>https://dev.to/seppegadeyne/hermes-agent-for-seo-from-search-console-to-a-blog-post-1ap0</guid>
      <description>&lt;p&gt;I don't want an AI agent to turn a keyword into another 1,500 words of plausible advice. I want it to inspect the site, find a question worth answering, read the relevant documentation, and give me an article whose claims I can check.&lt;/p&gt;

&lt;p&gt;Hermes Agent plus Google Search Console gets you part of the way there. Add reusable research and writing skills, your repository, and your own technical notes, and you have a workflow you can inspect instead of a conversation you hope went well.&lt;/p&gt;

&lt;p&gt;This tutorial covers installation, connecting Google Search Console, and using short skill commands to research, write, or audit an article. It adapts the process behind my work at Straffe Sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What you are connecting&lt;/li&gt;
&lt;li&gt;Install Hermes for your operating system&lt;/li&gt;
&lt;li&gt;Connect Search Console through gcloud&lt;/li&gt;
&lt;li&gt;Adapt the marketing skills for Hermes&lt;/li&gt;
&lt;li&gt;Turn the setup into an article&lt;/li&gt;
&lt;li&gt;Check the draft before you publish&lt;/li&gt;
&lt;li&gt;Troubleshooting before you blame the model&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What you are connecting
&lt;/h2&gt;

&lt;p&gt;There are three separate pieces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;What it does not provide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://hermes-agent.nousresearch.com/" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Runs the conversation and tools; reads files; follows skills&lt;/td&gt;
&lt;td&gt;A Google account or verified technical expertise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://developers.google.com/webmaster-tools?hl=en" rel="noopener noreferrer"&gt;Google Search Console API&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Returns search performance for properties you can access&lt;/td&gt;
&lt;td&gt;Market-wide keyword volume or an explanation of why rankings changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/nowork-studio/notfair-plugin" rel="noopener noreferrer"&gt;NotFair's open-source skills&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Supply research, audit, planning, and writing procedures&lt;/td&gt;
&lt;td&gt;An automatically working replacement for their default hosted connector&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You ask for a task; Hermes loads the relevant skill, uses the connected tools, and returns an audit or draft for you to review. The skill carries the detailed procedure, so your chat message can stay short.&lt;/p&gt;

&lt;p&gt;Your Google credentials stay in the local setup, but content passed to a cloud model may reach that model provider. Use approved accounts, keep credentials out of chat, and only share client data you are allowed to process.&lt;/p&gt;

&lt;p&gt;You will need &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt;, a model provider configured in Hermes, and a Google account. You or your administrator must be able to approve Cloud setup and access to the intended Search Console property. Hermes can help select or create the Cloud project instead of making you configure it in advance. The &lt;a href="https://www.gnu.org/software/bash/" rel="noopener noreferrer"&gt;Bash&lt;/a&gt; examples below target Linux, macOS, and &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install" rel="noopener noreferrer"&gt;WSL2&lt;/a&gt; on Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Hermes for your operating system
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Linux and Apple Silicon macOS
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://hermes-agent.nousresearch.com/docs/getting-started/installation" rel="noopener noreferrer"&gt;official Hermes installation guide&lt;/a&gt; provides this CLI installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://hermes-agent.nousresearch.com/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That executes a downloaded installer with your user's permissions and installs dependencies. If you prefer to review it first, use this route instead:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://hermes-agent.nousresearch.com/install.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; hermes-install.sh
less hermes-install.sh
&lt;span class="c"&gt;# Run only after reviewing the script and its dependencies.&lt;/span&gt;
bash hermes-install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open a new terminal if &lt;code&gt;hermes&lt;/code&gt; is not yet on your PATH. Then verify the CLI and run setup in the default Hermes profile:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes &lt;span class="nt"&gt;--version&lt;/span&gt;
hermes setup
hermes skills list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In setup, choose your model provider and follow its authentication flow. Hermes installation and access to a model are separate requirements. &lt;/p&gt;

&lt;p&gt;The current &lt;a href="https://hermes-agent.nousresearch.com/docs/getting-started/platform-support" rel="noopener noreferrer"&gt;platform support page&lt;/a&gt; lists Apple Silicon macOS as supported and Intel macOS as unsupported. Check that page before assuming an older Mac has the same installation path.&lt;/p&gt;
&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;p&gt;For the Bash examples in this article, use WSL2. In an administrator &lt;a href="https://learn.microsoft.com/en-us/powershell/" rel="noopener noreferrer"&gt;PowerShell&lt;/a&gt; window:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Restart if Windows requests it, open the installed Linux distribution, complete its initial user setup, and run the Linux installer above inside that shell. Have Hermes install &lt;a href="https://docs.cloud.google.com/sdk/docs/install-sdk?hl=en" rel="noopener noreferrer"&gt;Google Cloud CLI&lt;/a&gt; inside WSL too. Mixing a Windows Hermes process, WSL credentials, and Bash paths makes authentication harder to diagnose.&lt;/p&gt;

&lt;p&gt;Hermes also has an official native Windows installer. If you prefer PowerShell rather than WSL:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://hermes-agent.nousresearch.com/install.ps1&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;Review that script first if you would not normally execute a remote installer. Native Windows installation is an alternative, not an extra step after WSL. The remaining shell snippets here are Bash; don't paste them unchanged into PowerShell. The Google setup prompt below asks Hermes to handle installation and paths for your actual environment, including native Windows.&lt;/p&gt;

&lt;p&gt;For an introduction that also covers the desktop app, see &lt;a href="https://voltti.ai/en/insights/install-hermes-desktop-app-or-terminal" rel="noopener noreferrer"&gt;installing Hermes on Voltti&lt;/a&gt;. Use the official Hermes docs for current commands.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connect Search Console through gcloud
&lt;/h2&gt;

&lt;p&gt;Open Hermes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Replace &lt;code&gt;[MY_WEBSITE]&lt;/code&gt; with your site and give Hermes this prompt:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install gcloud and connect Google Search Console for [MY_WEBSITE]
using a service account. Add its email in GSC through browser control,
or give me the email to add myself. Use read-only access, ask me to
approve permissions, and test the connection.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hermes can work out the installation commands, Cloud project setup, and authentication details from the official documentation. You handle Google login, consent, and 2FA. If browser control is unavailable, a &lt;a href="https://support.google.com/webmasters/answer/7687615?hl=en" rel="noopener noreferrer"&gt;Search Console property owner&lt;/a&gt; can add the email under &lt;strong&gt;Settings → Users and permissions&lt;/strong&gt;. Restricted access is enough for performance reports.&lt;/p&gt;

&lt;p&gt;Keep command approvals enabled. Prefer &lt;a href="https://docs.cloud.google.com/iam/docs/service-account-impersonation" rel="noopener noreferrer"&gt;keyless service-account authentication&lt;/a&gt;, keep credentials out of chat and your repository, and don't grant site ownership or broad Cloud administrator roles just to read reports. The connection test must use the service account that was added to GSC, not a different personal account.&lt;/p&gt;

&lt;p&gt;You should finish with the correct property identified and a real API read, not just “setup complete.” If the property has no useful history yet, Hermes should say so. My &lt;a href="https://straffesites.com/en/blog/google-search-console" rel="noopener noreferrer"&gt;Search Console setup guide&lt;/a&gt; covers creating and verifying the property itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adapt the marketing skills for Hermes
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/nowork-studio/notfair-plugin" rel="noopener noreferrer"&gt;NotFair plugin&lt;/a&gt; provides reusable SEO, research, and writing procedures. Ask Hermes to adapt them to the Google connection you just set up:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adapt the SEO and Search Console skills from
https://github.com/nowork-studio/notfair-plugin for Hermes.
Use my gcloud service account instead of the hosted connector, name
them notfair-*, and keep Google access read-only. Show the changes
and test results before installing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hermes should inspect the source, include the required supporting files, and test the adaptation. You don't need to dictate the folder structure or API implementation. Review what it changed; once you are satisfied, reply:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install the reviewed skills.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;notfair-*&lt;/code&gt; names below are the local names requested in that prompt, not commands bundled with a fresh Hermes install. Start a new Hermes session after installation. If a command is missing, ask Hermes to check that the skill was installed and exposed under its intended command name.&lt;/p&gt;

&lt;p&gt;Voltti's &lt;a href="https://voltti.ai/en/insights/retrieve-marketing-data-with-ai-agent" rel="noopener noreferrer"&gt;guide to retrieving marketing data with an AI agent&lt;/a&gt; explains the broader approach. Here, the goal is simply to make the skills use your own working Google connection.&lt;/p&gt;
&lt;h2&gt;
  
  
  Turn the setup into an article
&lt;/h2&gt;

&lt;p&gt;In the Hermes chat box, start with &lt;strong&gt;&lt;code&gt;/skill-name&lt;/code&gt;&lt;/strong&gt;, then add a URL and what you want done. Type &lt;code&gt;/&lt;/code&gt; to see available commands. Hermes &lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/features/skills" rel="noopener noreferrer"&gt;loads the installed skill's instructions&lt;/a&gt; for you; you don't need to paste its whole procedure into the conversation.&lt;/p&gt;

&lt;p&gt;These examples go into &lt;strong&gt;Hermes chat, not Bash or PowerShell&lt;/strong&gt;. I use Straffe Sites URLs as examples. Replace them with your own site; a public URL does not grant access to its private Search Console data.&lt;/p&gt;

&lt;p&gt;Each block below contains the original prompt and the complete final answer from a recorded Hermes run. Intermediate tool activity is omitted. The writing follow-ups continue the same writing conversation; the audit follow-up continues its own audit conversation. Nothing was published.&lt;/p&gt;

&lt;p&gt;DEV currently labels these compatible trace imports as Claude Code. The agent that ran them was Hermes. Expand the longer answers and use the scrollbar to read them. DEV does not create section anchors inside these embeds, so an answer's contents links will not jump to its headings.&lt;/p&gt;
&lt;h3&gt;
  
  
  Write an article
&lt;/h3&gt;

&lt;p&gt;The skill supplies the writing process. Your prompt supplies the site and topic. Add an audience or angle when it matters, and let Hermes ask for anything it cannot establish from the context.&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Hermes Agent: writing Astro vs WordPress (compatible trace import)&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;gpt-6-astra&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;/notfair-content-writer &lt;a href="https://straffesites.com" rel="noopener noreferrer"&gt;https://straffesites.com&lt;/a&gt;&lt;br&gt;
Write an English article about Astro versus WordPress for small&lt;br&gt;
business websites.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1&gt;Blog article: Astro vs WordPress: choose for the person editing&lt;/h1&gt;

&lt;h2&gt;SEO metadata&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title tag:&lt;/strong&gt; Astro vs WordPress: choose for the person editing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta description:&lt;/strong&gt; Astro vs WordPress for small business websites: compare editing, speed, maintenance, and integrations. Use a practical checklist to choose your setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proposed URL slug:&lt;/strong&gt; &lt;code&gt;/en/blog/astro-vs-wordpress&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target keyword:&lt;/strong&gt; Astro vs WordPress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary keywords:&lt;/strong&gt; Astro for small business websites; WordPress for small business; Astro CMS; WordPress vs static website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search intent:&lt;/strong&gt; Commercial investigation, answered through an educational comparison.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audience:&lt;/strong&gt; Developers advising small businesses and owners evaluating website proposals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content angle:&lt;/strong&gt; Choose the editing and maintenance workflow first, then test the complete implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata checks:&lt;/strong&gt; Title: 49 characters. Description: 150 characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Editorial research and positioning&lt;/h2&gt;

&lt;h3&gt;Existing coverage and keyword evidence&lt;/h3&gt;

&lt;p&gt;No dedicated Astro-versus-WordPress article was found in the inspected content inventory or site search. Related coverage already exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://straffesites.com/en/blog/website-without-cms" rel="noopener noreferrer"&gt;A website without a CMS&lt;/a&gt;: the site's Astro and Git content workflow.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://straffesites.com/en/blog/astro-vs-next-js" rel="noopener noreferrer"&gt;Astro vs. Next.js&lt;/a&gt;: framework selection.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://straffesites.com/en/blog/wordpress-maintenance" rel="noopener noreferrer"&gt;WordPress maintenance&lt;/a&gt;: ongoing operational work.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://straffesites.com/en/blog/what-is-a-headless-cms" rel="noopener noreferrer"&gt;What is a headless CMS?&lt;/a&gt;: separating editing from presentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This draft keeps its focus on small-business platform selection rather than repeating those implementation guides.&lt;/p&gt;

&lt;p&gt;The verified keyword registry uses final Google Search Console data for &lt;code&gt;sc-domain:straffesites.com&lt;/code&gt;, covering September 1, 2025, through September 1, 2026. It contains no retained query combining “Astro” and “WordPress.” The proposed primary keyword therefore comes from the requested topic and live search research, not a demonstrated ranking opportunity in that registry.&lt;/p&gt;

&lt;p&gt;Relevant existing queries include:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Existing destination&lt;/th&gt;
&lt;th&gt;Recorded query&lt;/th&gt;
&lt;th&gt;Clicks&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Headless CMS article&lt;/td&gt;
&lt;td&gt;what is a headless cms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WordPress maintenance article&lt;/td&gt;
&lt;td&gt;wordpress development and maintenance&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;182&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Astro glossary entry&lt;/td&gt;
&lt;td&gt;astro framework&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are query impressions for this property and period, not search volumes. The low-click clusters are indicative, and absent queries do not establish absent demand.&lt;/p&gt;

&lt;h3&gt;Search-result review&lt;/h3&gt;

&lt;p&gt;The first five returned results included:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Format and emphasis&lt;/th&gt;
&lt;th&gt;Editorial response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://webaloha.co/wordpress-vs-astro-for-business/" rel="noopener noreferrer"&gt;Web Aloha&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Broad comparison with performance, security, and cost claims&lt;/td&gt;
&lt;td&gt;Explain caching and avoid treating platform defaults as guaranteed outcomes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/r/smallbusiness/comments/1t6zldz/astro_code_or_wordpress_for_businesses_websites/" rel="noopener noreferrer"&gt;Reddit discussion&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Community discussion about editing and maintenance ownership&lt;/td&gt;
&lt;td&gt;Full discussion unavailable: extraction failed and the browser showed a human-verification page. No anecdote from it is used as evidence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://andrewwalpole.com/blog/consider-astro-over-wordpress/" rel="noopener noreferrer"&gt;Andrew Walpole&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Developer perspective on custom brand sites and content architecture&lt;/td&gt;
&lt;td&gt;Bring the decision down to small-business editing tasks and handover requirements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.mfbsolutions.hr/en/blog/wordpress-vs-astro-for-small-business" rel="noopener noreferrer"&gt;MFB Solutions&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Small-business comparison favoring Astro&lt;/td&gt;
&lt;td&gt;Correct the implication that static delivery eliminates technical maintenance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dizzidigital.com/blog/wordpress-vs-astro-business-website/" rel="noopener noreferrer"&gt;Dizzi Digital&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Business-purpose comparison and migration discussion&lt;/td&gt;
&lt;td&gt;Add a concrete acceptance test for editing, publishing, integrations, and recovery.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Technical claims below rely on official documentation rather than competitors' benchmarks. The business scenario is explicitly hypothetical. No speed experiment, client result, or ranking uplift is invented.&lt;/p&gt;

&lt;h2&gt;Content&lt;/h2&gt;

&lt;h1&gt;Astro vs WordPress: choose for the person editing&lt;/h1&gt;

&lt;p&gt;Astro vs WordPress is often sold as a speed contest. For a small business, the more expensive mistake may be choosing a site that nobody can update. Choose Astro when a developer will maintain a mostly informational site; choose WordPress when the business needs a familiar publishing dashboard or depends on established plugins. A fast homepage cannot compensate for an outdated service page.&lt;/p&gt;

&lt;p&gt;This comparison is for developers advising small businesses and owners reviewing their recommendations. WordPress here means the open-source CMS in a conventional hosted setup, not a particular WordPress.com plan. Astro means the web framework, with static delivery as the starting point rather than a restriction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astro vs WordPress: the practical comparison&lt;/li&gt;
&lt;li&gt;Test editing before choosing the stack&lt;/li&gt;
&lt;li&gt;Compare finished pages, not framework promises&lt;/li&gt;
&lt;li&gt;Account for maintenance on both sides&lt;/li&gt;
&lt;li&gt;Check forms, bookings, and checkout early&lt;/li&gt;
&lt;li&gt;Use headless WordPress only for a specific reason&lt;/li&gt;
&lt;li&gt;Work through a small-business example&lt;/li&gt;
&lt;li&gt;Decide whether a migration is worth doing&lt;/li&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Astro vs WordPress: the practical comparison&lt;/h2&gt;

&lt;p&gt;WordPress combines content management with website rendering. Its &lt;a href="https://wordpress.org/documentation/article/wordpress-block-editor/" rel="noopener noreferrer"&gt;block editor&lt;/a&gt; gives editors a browser interface for headings, images, paragraphs, and other content. Themes control presentation, while plugins can add features.&lt;/p&gt;

&lt;p&gt;Astro is a framework for building the website. It does not supply an equivalent editorial dashboard by itself. Developers can use content files, connect a CMS, or combine sources. Its &lt;a href="https://docs.astro.build/en/guides/on-demand-rendering/" rel="noopener noreferrer"&gt;default rendering mode&lt;/a&gt; builds pages ahead of time; routes that need fresh or personalized responses can render on demand with an adapter and suitable runtime.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Astro, starting with static pages&lt;/th&gt;
&lt;th&gt;Conventional WordPress&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who changes content?&lt;/td&gt;
&lt;td&gt;A developer, or editors using a separately configured CMS&lt;/td&gt;
&lt;td&gt;Editors using the built-in dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How do visitors receive pages?&lt;/td&gt;
&lt;td&gt;Prebuilt HTML for static routes; server rendering where configured&lt;/td&gt;
&lt;td&gt;Generated pages or cached responses, depending on the setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How are features added?&lt;/td&gt;
&lt;td&gt;Components, integrations, external services, or custom code&lt;/td&gt;
&lt;td&gt;Core features, plugins, external services, or custom code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What needs maintenance?&lt;/td&gt;
&lt;td&gt;Dependencies, build and deployment workflow, hosting, accounts, and integrations&lt;/td&gt;
&lt;td&gt;Core, themes, plugins, hosting, database, accounts, and integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where does it fit well?&lt;/td&gt;
&lt;td&gt;Content-focused sites with deliberate control over the frontend&lt;/td&gt;
&lt;td&gt;Sites that benefit from an established editor and plugin workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither column describes a maintenance-free product. Compare the complete setup the client will receive, including the editing interface and support arrangement.&lt;/p&gt;

&lt;h2&gt;Test editing before choosing the stack&lt;/h2&gt;

&lt;h3&gt;Ask the eventual editor to do real work&lt;/h3&gt;

&lt;p&gt;Before approving a platform, ask the person who will maintain the content to change opening hours, replace a photo, and publish a service update in a preview environment. Give them the proposed interface, not a presentation about it.&lt;/p&gt;

&lt;p&gt;Watch where they need help. Can they preview the page on a phone? Can they correct a mistake without contacting the developer? Can another employee take over? A technically elegant setup fails this test if routine changes require skills the business does not have.&lt;/p&gt;

&lt;p&gt;WordPress supplies the basic editing interface. Its usability still depends on the theme, blocks, permissions, and customizations. A cluttered dashboard with unrestricted layout controls can be harder to use than a small set of well-designed fields.&lt;/p&gt;

&lt;h3&gt;Make Astro's content workflow explicit&lt;/h3&gt;

&lt;p&gt;An Astro proposal should name the content workflow: developer-managed files, a Git-based editor, or a headless CMS. These are different products for the business, even when visitors receive similar HTML.&lt;/p&gt;

&lt;p&gt;Straffe Sites provides a concrete example of the file-based option: its &lt;a href="https://straffesites.com/en/blog/website-without-cms" rel="noopener noreferrer"&gt;website without a CMS&lt;/a&gt; uses Astro Content Collections with MDX and JSON content in Git. That demonstrates a workable approach for a technical team. It does not show that a shop owner should learn Git to change holiday hours.&lt;/p&gt;

&lt;p&gt;If editors need a CMS, include it from the start. Agree on preview access, publication timing, image handling, and who responds when an update fails. &lt;strong&gt;Choose the editing workflow before treating Astro as the finished solution.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;Compare finished pages, not framework promises&lt;/h2&gt;

&lt;h3&gt;Astro provides useful defaults, not a guaranteed score&lt;/h3&gt;

&lt;p&gt;Astro's &lt;a href="https://docs.astro.build/en/concepts/islands/" rel="noopener noreferrer"&gt;islands architecture&lt;/a&gt; lets developers render UI components as HTML and add client-side JavaScript where interactive components need it. A service page can remain mostly HTML while a calculator receives its own interactive code.&lt;/p&gt;

&lt;p&gt;That can reduce unnecessary browser work. It does not make large photos smaller by itself, remove an embedded booking widget, or stop a developer from adding expensive scripts. Ordinary script tags and third-party embeds still count toward the visitor's workload.&lt;/p&gt;

&lt;p&gt;WordPress is not required to rebuild every public page for every visitor. Its &lt;a href="https://developer.wordpress.org/advanced-administration/performance/cache/" rel="noopener noreferrer"&gt;official caching documentation&lt;/a&gt; describes serving cached static files. A well-configured page cache changes the comparison substantially; dynamic and personalized pages need separate consideration.&lt;/p&gt;

&lt;h3&gt;Agree on a fair test&lt;/h3&gt;

&lt;p&gt;Compare representative pages with the content and features the business will actually use. Include the same images, fonts, analytics, and booking tools. Test a service page and an article, not just a nearly empty homepage.&lt;/p&gt;

&lt;p&gt;Check loading behavior and responsiveness on mobile, keyboard navigation, layout stability, and whether the main action works. Record the test conditions. Lab tests help diagnose problems; available real-user data describes a different population and should not be presented as the same measurement.&lt;/p&gt;

&lt;p&gt;Google confirms that &lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;Core Web Vitals are used by its ranking systems&lt;/a&gt;, but good scores do not guarantee top rankings. Neither platform replaces useful content, crawlable links, or accurate service information. This article reports no controlled Astro-versus-WordPress experiment, so it makes no percentage-speedup or ranking-uplift claim.&lt;/p&gt;

&lt;p&gt;The acceptance criterion should be a usable, measured website, not the framework name on the proposal.&lt;/p&gt;

&lt;h2&gt;Account for maintenance on both sides&lt;/h2&gt;

&lt;h3&gt;WordPress maintenance has a familiar shape&lt;/h3&gt;

&lt;p&gt;WordPress needs a plan for core, theme, and plugin updates, backups, access control, and recovery. Its &lt;a href="https://developer.wordpress.org/advanced-administration/security/hardening/" rel="noopener noreferrer"&gt;security guidance&lt;/a&gt; describes security as risk reduction rather than risk elimination. Managed hosting can take responsibility for some tasks; check which ones the agreement actually covers.&lt;/p&gt;

&lt;p&gt;Test important functions after changes. For a service business, a working contact form matters more than a dashboard that says every plugin is current. The separate guide to &lt;a href="https://straffesites.com/en/blog/wordpress-maintenance" rel="noopener noreferrer"&gt;WordPress maintenance&lt;/a&gt; covers the work in more detail.&lt;/p&gt;

&lt;h3&gt;Static delivery removes some responsibilities, not all of them&lt;/h3&gt;

&lt;p&gt;A fully static Astro frontend does not need a WordPress application or database to serve its pages. Removing that public application layer removes the need to maintain it. But the repository, dependencies, deployment accounts, domain, and hosting still need care. Forms, APIs, and any connected CMS bring their own responsibilities.&lt;/p&gt;

&lt;p&gt;A compromised deployment account can replace a static site. A broken dependency can prevent the next build. If the site uses on-demand rendering, its runtime also needs maintenance. “Nothing to patch” is not a credible handover promise.&lt;/p&gt;

&lt;p&gt;Compare ownership costs across the same scope: implementation, hosting, editor support, integrations, updates, recovery, and eventual handover. Do not compare basic static hosting with a WordPress agreement that also includes technical support. Ask who owns each task and what happens when that person is unavailable.&lt;/p&gt;

&lt;h2&gt;Check forms, bookings, and checkout early&lt;/h2&gt;

&lt;p&gt;A static page can display a form, but submitting it still needs a receiving service or server endpoint. Choose that destination before launch. Test validation, spam controls, delivery failures, and the confirmation message. A success screen should mean the receiving system accepted the request, not merely that the visitor clicked a button.&lt;/p&gt;

&lt;p&gt;For appointments, distinguish a link to an existing booking service from a custom booking application. A local consultant who already uses a scheduling service may need only a clear link. A business managing staff calendars, deposits, cancellations, and customer accounts has a larger integration problem.&lt;/p&gt;

&lt;p&gt;The same distinction applies to selling online. WordPress can use a commerce plugin such as &lt;a href="https://woocommerce.com/" rel="noopener noreferrer"&gt;WooCommerce&lt;/a&gt;. Astro can connect to a commerce backend, but the framework itself is not an order-management system. Define who handles products, inventory, payments, refunds, and customer notifications before selecting the frontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List the business operations first.&lt;/strong&gt; If an established WordPress plugin already supports them well, replacing it with custom integrations needs a specific benefit. If the business already runs those operations in another service, its website may not need to duplicate them.&lt;/p&gt;

&lt;h2&gt;Use headless WordPress only for a specific reason&lt;/h2&gt;

&lt;p&gt;Astro and WordPress can work together. In a headless setup, WordPress manages content while Astro renders the public website. The &lt;a href="https://docs.astro.build/en/guides/cms/wordpress/" rel="noopener noreferrer"&gt;official Astro integration guide&lt;/a&gt; explains how to retrieve content through the WordPress REST API.&lt;/p&gt;

&lt;p&gt;This is useful when editors need to retain WordPress while developers need a separate frontend. The guide to &lt;a href="https://straffesites.com/en/blog/what-is-a-headless-cms" rel="noopener noreferrer"&gt;how a headless CMS works&lt;/a&gt; explains that separation more broadly.&lt;/p&gt;

&lt;p&gt;It also leaves two systems to operate. For a statically built frontend, publishing in WordPress must trigger a successful build and deployment before visitors see the new content. Draft previews, failed builds, redirects, and cache invalidation need deliberate handling. WordPress plugins that expect to render their own frontend do not automatically become Astro components.&lt;/p&gt;

&lt;p&gt;Keep private drafts behind appropriate authentication. Treat HTML returned by a CMS according to its trust boundary; rendering a string as HTML is not a substitute for controlling who can supply it.&lt;/p&gt;

&lt;p&gt;Choose this combination to preserve a valuable editorial workflow or meet a frontend requirement. It is usually unnecessary complexity if a conventional WordPress site already does the job.&lt;/p&gt;

&lt;h2&gt;Work through a small-business example&lt;/h2&gt;

&lt;p&gt;Consider a hypothetical electrical contractor with eight service pages, a project gallery, a contact form, and an existing external scheduling tool. The owner changes service descriptions occasionally and wants someone else to handle technical maintenance. These are example requirements, not a client case or measured experiment.&lt;/p&gt;

&lt;p&gt;Astro is a reasonable starting point. The service pages can be prebuilt, the gallery can use optimized images, and the booking action can link to the scheduling tool. The proposal still needs to explain how the owner requests content changes and how urgent corrections are handled.&lt;/p&gt;

&lt;p&gt;Now change one requirement: an office manager must publish new projects independently every week. Astro can still work, but a suitable CMS becomes part of the proposal. WordPress deserves a fresh comparison because its editor may meet that requirement with less integration work.&lt;/p&gt;

&lt;p&gt;Change another requirement: the business already relies on a WordPress plugin to coordinate bookings and payments. Keeping WordPress may now be the lower-risk choice. Rebuilding a working operational system just to change the frontend is a much larger decision than replacing a brochure site.&lt;/p&gt;

&lt;p&gt;Use this exercise in discovery. Change the requirements and see whether the recommendation changes. If the answer is always the developer's favorite framework, the evaluation is not doing its job.&lt;/p&gt;

&lt;h2&gt;Decide whether a migration is worth doing&lt;/h2&gt;

&lt;p&gt;A working WordPress site does not need replacement because Astro exists. First identify the problem: slow pages, an unusable editor, excessive maintenance, or a design that no longer fits the business. Some problems can be fixed without changing platforms.&lt;/p&gt;

&lt;p&gt;If a migration is justified, make an inventory of existing URLs, content, images, metadata, forms, and integrations. Preserve useful URLs where possible. Map changed URLs to relevant destinations instead of sending everything to the homepage, and check internal links after the move.&lt;/p&gt;

&lt;p&gt;Avoid these common shortcuts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comparing a minimal Astro demo with an unoptimized production WordPress site.&lt;/li&gt;
&lt;li&gt;Calling static delivery maintenance-free.&lt;/li&gt;
&lt;li&gt;Assuming every editor needs a CMS, or assuming nobody does.&lt;/li&gt;
&lt;li&gt;Replacing a working booking or checkout workflow without testing its replacement.&lt;/li&gt;
&lt;li&gt;Promising better rankings simply because the framework changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before signing off, request a demonstration of editing, publishing, and recovery, plus a written handover plan. Choose Astro for a content-focused site with an agreed editing workflow and capable technical ownership. Choose WordPress when its editor or established integrations solve the business's needs more directly. Keep the current site when the migration cannot justify its disruption.&lt;/p&gt;

&lt;h2&gt;Frequently asked questions&lt;/h2&gt;

&lt;h3&gt;Is Astro better than WordPress for a small business?&lt;/h3&gt;

&lt;p&gt;Astro is a strong option for a content-focused site with an agreed editing workflow and technical support. WordPress is often the more direct choice when staff need its built-in editor or established plugins. Choose the complete setup, not the framework in isolation.&lt;/p&gt;

&lt;h3&gt;Can a nontechnical owner update an Astro website?&lt;/h3&gt;

&lt;p&gt;Yes, if the project includes an appropriate CMS or editing interface. Astro alone does not provide a WordPress-style dashboard. Without an editor, content changes usually go through someone comfortable with the project's files and publishing workflow.&lt;/p&gt;

&lt;h3&gt;Does switching to Astro improve SEO automatically?&lt;/h3&gt;

&lt;p&gt;No. Astro can support a lightweight implementation, but rankings also depend on content relevance and other signals. A migration must preserve useful URLs, internal links, metadata, and indexability. Good performance scores do not guarantee higher rankings.&lt;/p&gt;

&lt;h3&gt;Can Astro use WordPress as its CMS?&lt;/h3&gt;

&lt;p&gt;Yes. Astro can retrieve content through the WordPress REST API while WordPress remains the editing system. The project still needs WordPress maintenance, frontend deployment, and a tested approach to previews and publishing.&lt;/p&gt;



&lt;h2&gt;Image concepts&lt;/h2&gt;

&lt;p&gt;Image production is outside this demonstration's scope. These are concepts, not existing assets. No placeholder image tags have been inserted. The inline concepts are optional editorial suggestions, not instructions to override the site's image policy.&lt;/p&gt;

&lt;h3&gt;Featured image: two publishing workflows&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role:&lt;/strong&gt; Explanatory editorial illustration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placement:&lt;/strong&gt; After the opening paragraph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggested filename:&lt;/strong&gt; &lt;code&gt;astro-vs-wordpress-publishing-workflows.webp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alt text:&lt;/strong&gt; “A browser editor and a file-based publishing workflow both leading to a finished business website.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt:&lt;/strong&gt; Create a wide 16:9 editorial 3D illustration showing two equally credible publishing routes. On the left, a browser editing panel with content blocks; on the right, document files passing through a build stage. Both connect to the same finished small-business website. Use a warm beige #E9E6DF background, substantial charcoal forms, paper-colored panels, and several lime accents. Crisp edges, restrained glass details, clear directional connections, studio lighting. Avoid winner trophies, speed gauges, logos, fabricated screenshots, tiny text, people at laptops, and decorative clutter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Inline concept: the editing acceptance test&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role:&lt;/strong&gt; Workflow diagram.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placement:&lt;/strong&gt; “Test editing before choosing the stack.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggested filename:&lt;/strong&gt; &lt;code&gt;website-editor-acceptance-test.webp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alt text:&lt;/strong&gt; “Editing test showing an opening-hours change, photo replacement, preview, and publication.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt:&lt;/strong&gt; Design a readable 4:3 workflow diagram with four connected stages: edit opening hours, replace a photo, preview the page, publish the update. Show recognizable content panels rather than brand-specific interfaces. Use beige #E9E6DF, charcoal, paper, and lime. Give every stage generous space. Add labels during design production for accurate typography. Avoid fake software screenshots, approval badges, decorative people, and implied measured results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Inline concept: what visitors receive&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role:&lt;/strong&gt; Architecture comparison.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placement:&lt;/strong&gt; “Compare finished pages, not framework promises.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggested filename:&lt;/strong&gt; &lt;code&gt;astro-wordpress-page-delivery.webp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alt text:&lt;/strong&gt; “Static Astro delivery beside WordPress delivery with separate cache-hit and cache-miss paths.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt:&lt;/strong&gt; Create a wide technical diagram with two horizontal lanes. Astro: content and templates, build, static files, visitor. WordPress: visitor request, page cache, cached response; show a separate cache-miss branch to application and database before returning HTML. Add a small annotation area for Astro's optional on-demand rendering. Use matching visual weight for both lanes, beige background, charcoal connectors, and lime route highlights. Avoid latency numbers, winner labels, or implying every WordPress visit queries a database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Inline concept: maintenance ownership&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role:&lt;/strong&gt; Responsibility diagram.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placement:&lt;/strong&gt; “Account for maintenance on both sides.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggested filename:&lt;/strong&gt; &lt;code&gt;website-maintenance-responsibilities.webp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alt text:&lt;/strong&gt; “Shared website responsibilities alongside WordPress application maintenance and Astro build maintenance.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt:&lt;/strong&gt; Create a 4:3 responsibility diagram. Put shared tasks in a central column: domain, accounts, hosting, integrations, recovery. Place WordPress core, themes, plugins, and database on one side; Astro dependencies, build workflow, and optional runtime or CMS on the other. Use paper cards over warm beige, charcoal typography, and lime grouping markers. Keep the mood practical and neutral. Avoid shields suggesting immunity, empty Astro maintenance columns, numerical risk scores, and logos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Structured data&lt;/h2&gt;

&lt;p&gt;The following draft JSON-LD parses successfully, and all four FAQ questions and answers match the article.&lt;/p&gt;

&lt;p&gt;Publication-specific fields are deliberately absent: &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;mainEntityOfPage&lt;/code&gt;, author identity, and publication dates must come from the approved publication and real assets. This is a draft schema, not a claim that a page or image has been published.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FAQPage&lt;/code&gt; remains a Schema.org type, but Google's &lt;a href="https://developers.google.com/search/updates#removing-faq-rich-result" rel="noopener noreferrer"&gt;documentation changelog&lt;/a&gt; states that FAQ rich results are no longer shown. This markup is not a promise of search-result enhancements.&lt;/p&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;"@context"&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://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@graph"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BlogPosting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Astro vs WordPress: choose for the person editing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Astro vs WordPress for small business websites: compare editing, speed, maintenance, and integrations. Use a practical checklist to choose your setup."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"about"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Astro"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WordPress"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAQPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mainEntity"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is Astro better than WordPress for a small business?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Astro is a strong option for a content-focused site with an agreed editing workflow and technical support. WordPress is often the more direct choice when staff need its built-in editor or established plugins. Choose the complete setup, not the framework in isolation."&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;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can a nontechnical owner update an Astro website?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes, if the project includes an appropriate CMS or editing interface. Astro alone does not provide a WordPress-style dashboard. Without an editor, content changes usually go through someone comfortable with the project's files and publishing workflow."&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;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does switching to Astro improve SEO automatically?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No. Astro can support a lightweight implementation, but rankings also depend on content relevance and other signals. A migration must preserve useful URLs, internal links, metadata, and indexability. Good performance scores do not guarantee higher rankings."&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;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can Astro use WordPress as its CMS?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes. Astro can retrieve content through the WordPress REST API while WordPress remains the editing system. The project still needs WordPress maintenance, frontend deployment, and a tested approach to previews and publishing."&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;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;h2&gt;Internal linking plan&lt;/h2&gt;

&lt;h3&gt;This article links to&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Destination&lt;/th&gt;
&lt;th&gt;Anchor used&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://straffesites.com/en/blog/website-without-cms" rel="noopener noreferrer"&gt;Website without a CMS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;website without a CMS&lt;/td&gt;
&lt;td&gt;Show the site's documented file-based content workflow.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://straffesites.com/en/blog/wordpress-maintenance" rel="noopener noreferrer"&gt;WordPress maintenance&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;WordPress maintenance&lt;/td&gt;
&lt;td&gt;Keep detailed maintenance coverage on its existing page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://straffesites.com/en/blog/what-is-a-headless-cms" rel="noopener noreferrer"&gt;What is a headless CMS?&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;how a headless CMS works&lt;/td&gt;
&lt;td&gt;Explain the architecture without expanding this comparison into a separate CMS tutorial.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The headless anchor follows a recorded query variation. The maintenance anchor is a natural topic description; no ranking claim is made for that exact phrasing. The newer no-CMS article has no demonstrated cluster in the inspected snapshot.&lt;/p&gt;

&lt;h3&gt;Suggested links to this article&lt;/h3&gt;

&lt;p&gt;These are recommendations only; no links were added.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the no-CMS article, beside the discussion of nontechnical editors: “Astro vs WordPress for a small business.”&lt;/li&gt;
&lt;li&gt;From the WordPress maintenance article, when discussing rebuilding versus maintaining: “when switching from WordPress makes sense.”&lt;/li&gt;
&lt;li&gt;From the Astro-versus-Next.js article, beside the conventional-CMS alternative: “compare Astro with WordPress.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Publishing checklist&lt;/h2&gt;

&lt;h3&gt;Completed for this demonstration&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] Full article delivered in US English.&lt;/li&gt;
&lt;li&gt;[x] Article exceeds 1,000 words.&lt;/li&gt;
&lt;li&gt;[x] Hook-driven title and metadata lengths checked.&lt;/li&gt;
&lt;li&gt;[x] Existing coverage and verified keyword clusters inspected.&lt;/li&gt;
&lt;li&gt;[x] Official sources consulted for technical claims.&lt;/li&gt;
&lt;li&gt;[x] Hypothetical scenario clearly distinguished from client evidence.&lt;/li&gt;
&lt;li&gt;[x] Table of contents mirrors the article's H2 sections.&lt;/li&gt;
&lt;li&gt;[x] Four visible FAQ answers supplied.&lt;/li&gt;
&lt;li&gt;[x] Draft JSON-LD parsed and FAQ text parity verified.&lt;/li&gt;
&lt;li&gt;[x] Final language pass removed promotional claims and unsupported certainty.&lt;/li&gt;
&lt;li&gt;[x] Website, Git, GitHub, authentication, and third-party content left unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Required only if publication is later approved&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Confirm the route and recheck overlapping coverage.&lt;/li&gt;
&lt;li&gt;[ ] Obtain editorial approval and confirm the author byline.&lt;/li&gt;
&lt;li&gt;[ ] Set actual publication and modification dates.&lt;/li&gt;
&lt;li&gt;[ ] Produce the approved featured image; set alt text and Open Graph metadata.&lt;/li&gt;
&lt;li&gt;[ ] Decide whether any optional inline diagrams fit the site's editorial policy.&lt;/li&gt;
&lt;li&gt;[ ] Verify asset format, dimensions, and file size.&lt;/li&gt;
&lt;li&gt;[ ] Populate structured-data image and publication fields with verified values.&lt;/li&gt;
&lt;li&gt;[ ] Integrate with existing schema output without duplicate article or FAQ nodes.&lt;/li&gt;
&lt;li&gt;[ ] Configure the appropriate canonical URL for the publication destination.&lt;/li&gt;
&lt;li&gt;[ ] Add approved contextual links in both directions.&lt;/li&gt;
&lt;li&gt;[ ] Verify rendered links, heading anchors, mobile tables, and desktop/mobile layout.&lt;/li&gt;
&lt;li&gt;[ ] If published, assess Search Console results after at least 30 days.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the complete demonstration draft and writing package. Nothing has been published.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;This is writing output to review, not an approved article. A short follow-up narrows the same draft to developers maintaining the site:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Hermes Agent: focus the outline on developers&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;gpt-6-astra&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Focus on developers maintaining the site. Show me the outline first.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Proposed title&lt;br&gt;
Astro vs WordPress: what developers maintain after launch&lt;/p&gt;

&lt;p&gt;Audience&lt;br&gt;
Developers responsible for small business websites after handover: content changes, dependency updates, forms, production failures, and eventual migration.&lt;/p&gt;

&lt;p&gt;Angle&lt;br&gt;
Compare the work each setup leaves you with. Keep the earlier draft's distinctions around caching, headless WordPress, and editing, but move the focus from the owner's platform choice to the maintainer's responsibilities.&lt;/p&gt;

&lt;p&gt;Outline&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose the maintenance model before the framework&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open with the recommendation:&lt;br&gt;
   - Static Astro is a strong fit when the site mostly publishes content and its publishing workflow has a clear technical owner.&lt;br&gt;
   - Conventional WordPress is often more practical when its editor and established plugins already meet the business's needs.&lt;br&gt;
   - Keeping a working site is a valid outcome.&lt;/p&gt;

&lt;p&gt;Define the comparison: static-first Astro versus conventional WordPress with sensible caching and maintenance. Cover on-demand Astro and headless WordPress as separate configurations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What changes when the public site becomes static?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Explain the request path briefly, then connect it to operational work:&lt;br&gt;
   - Prebuilt Astro pages versus cached and uncached WordPress responses.&lt;br&gt;
   - What removing a public PHP application and database removes from maintenance.&lt;br&gt;
   - What remains: dependencies, deployment access, hosting, domains, and integrations.&lt;br&gt;
   - How adding SSR changes the responsibilities again.&lt;/p&gt;

&lt;p&gt;Include one compact comparison table organized by maintenance task, not feature scores.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Follow a content change from request to production&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Trace the same change through both setups: update a service description and replace its image.&lt;/p&gt;

&lt;p&gt;Cover:&lt;br&gt;
   - WordPress editing, preview, publication, and cache invalidation.&lt;br&gt;
   - Astro file changes or CMS updates, validation, build, and deployment.&lt;br&gt;
   - Diagnosing “published in the editor, missing on the website.”&lt;br&gt;
   - Who handles urgent corrections when the usual maintainer is unavailable.&lt;/p&gt;

&lt;p&gt;Treat the editor as part of the system the developer supports, not a separate convenience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updates and recovery: what happens when something breaks?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use two clearly labeled hypothetical incidents:&lt;br&gt;
   - A WordPress plugin update breaks the contact form.&lt;br&gt;
   - An Astro dependency update prevents deployment.&lt;/p&gt;

&lt;p&gt;Explain:&lt;br&gt;
   - Staging, backups, dependency locks, and focused regression checks.&lt;br&gt;
   - The difference between a failed build and a broken live deployment.&lt;br&gt;
   - Why a code rollback does not necessarily reverse database changes.&lt;br&gt;
   - What a recovery procedure must cover beyond “revert the commit.”&lt;/p&gt;

&lt;p&gt;Takeaway: define and test recovery for the actual deployment setup.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Forms and bookings often determine the workload&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Follow the business-critical path beyond the frontend:&lt;br&gt;
   - Form submission, validation, spam protection, and delivery.&lt;br&gt;
   - Embedded scheduling tools versus booking plugins or custom integrations.&lt;br&gt;
   - Payments, webhooks, and external service failures where relevant.&lt;br&gt;
   - Checks that prove an inquiry reached its destination.&lt;/p&gt;

&lt;p&gt;Show when replacing a working plugin creates more maintenance than it removes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Headless WordPress: keep the editor, accept another system&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Explain when Astro plus WordPress is justified.&lt;/p&gt;

&lt;p&gt;Cover the additional work:&lt;br&gt;
   - API access and private previews.&lt;br&gt;
   - Build triggers, publication delays, and failed updates.&lt;br&gt;
   - Rendering blocks and features that previously depended on the theme.&lt;br&gt;
   - Maintaining both WordPress and the Astro frontend.&lt;/p&gt;

&lt;p&gt;Keep this section short: an operational trade-off, not an integration tutorial.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the decision against the site you actually maintain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apply the comparison to a hypothetical small service-business site. Change its requirements to show when the recommendation changes.&lt;/p&gt;

&lt;p&gt;End with a practical checklist:&lt;br&gt;
   - Who edits, and how urgently?&lt;br&gt;
   - Which integrations already work?&lt;br&gt;
   - Who owns updates and recovery?&lt;br&gt;
   - What can another developer take over?&lt;br&gt;
   - Could the current site's problems be fixed without migrating?&lt;/p&gt;

&lt;p&gt;Include performance checks here: representative pages, real scripts and embeds, consistent test conditions, and no automatic SEO benefit.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Does a static Astro site still need dependency updates?&lt;/li&gt;
&lt;li&gt;Can a failed Astro build take the live website down?&lt;/li&gt;
&lt;li&gt;Does managed WordPress hosting remove the developer's maintenance work?&lt;/li&gt;
&lt;li&gt;When does headless WordPress justify maintaining two systems?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essential editorial decisions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use developer-to-developer language. Explain business consequences without turning the article into a sales pitch.&lt;/li&gt;
&lt;li&gt;Keep the public Straffe Sites content-in-Git example, but use it only as architecture evidence, not proof that Astro wins universally.&lt;/li&gt;
&lt;li&gt;Replace the earlier owner's editing test with publishing, failure diagnosis, and recovery workflows.&lt;/li&gt;
&lt;li&gt;Avoid fabricated maintenance hours, cost savings, benchmarks, or first-person incident stories.&lt;/li&gt;
&lt;li&gt;Keep detailed migration instructions and framework setup code out of scope.&lt;/li&gt;
&lt;li&gt;End with a maintenance decision checklist rather than a contact CTA.&lt;/li&gt;
&lt;li&gt;Visual concepts only: a publishing-flow diagram and a recovery-path comparison. No images or article implementation at this stage.&lt;/li&gt;
&lt;/ul&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;An outline lets you check the angle before another writing pass. Give Hermes your own code, notes, or test results when you want first-hand examples; the agent cannot invent your experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audit an existing page
&lt;/h3&gt;

&lt;p&gt;The URL is enough to request a page audit. Hermes loads the audit skill, examines the page, and identifies improvements. If its GSC connection has access to that property, it can use the search data too; otherwise it should explain that limitation.&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Hermes Agent: SEO audit (compatible trace import)&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;gpt-6-astra&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;/notfair-seo-page &lt;a href="https://straffesites.com/en" rel="noopener noreferrer"&gt;https://straffesites.com/en&lt;/a&gt;&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1&gt;Page SEO Analysis — &lt;a href="https://straffesites.com/en" rel="noopener noreferrer"&gt;https://straffesites.com/en&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;September 13, 2026 · GSC data: June 13–September 10, 2026 · 90 days, final data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The page is indexed, its canonical is correct, and its technical foundation is strong. The main opportunity is clearer, better-qualified commercial messaging—not an indexability repair.&lt;/p&gt;

&lt;p&gt;The audited URL is the English business homepage, not a developer tutorial. It should help prospective clients understand the service, assess the evidence, and contact the team. Tutorial content is supporting material.&lt;/p&gt;

&lt;h2&gt;Overall Score: 7.8/10&lt;/h2&gt;

&lt;p&gt;These are editorial assessments, not Google scores, PageSpeed measurements, or ranking predictions. GSC metrics and browser observations are identified separately.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;Weighted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Search Intent Alignment&lt;/td&gt;
&lt;td&gt;8.0/10&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;1.600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-E-A-T Signals&lt;/td&gt;
&lt;td&gt;7.5/10&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;1.500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content Quality &amp;amp; Depth&lt;/td&gt;
&lt;td&gt;7.0/10&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;1.400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-Page SEO&lt;/td&gt;
&lt;td&gt;8.5/10&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;1.275&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content Structure &amp;amp; UX&lt;/td&gt;
&lt;td&gt;8.0/10&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;1.200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical SEO&lt;/td&gt;
&lt;td&gt;8.5/10&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;0.850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overall&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.825 → 7.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;Why these scores&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;What earns the score&lt;/th&gt;
&lt;th&gt;What prevents a higher score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Search intent&lt;/td&gt;
&lt;td&gt;Services, portfolio, process, and “Discuss your project” fit a commercial homepage.&lt;/td&gt;
&lt;td&gt;The headline emphasizes speed without immediately identifying the small-business audience. Actual GSC queries are sparse and ambiguous.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-E-A-T&lt;/td&gt;
&lt;td&gt;Named contact, 12 linked case studies, linked Google reviews, technical explanations, and an HTTP Archive citation.&lt;/td&gt;
&lt;td&gt;Hero performance claims lack an adjacent measurement date, sample definition, and clearly named loading metric.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content quality&lt;/td&gt;
&lt;td&gt;Covers services, examples, process, editing, support, and terminology.&lt;/td&gt;
&lt;td&gt;Buyers get limited detail about project scope and delivery dependencies. A substantial part of the content is article-card text rather than buyer guidance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-page SEO&lt;/td&gt;
&lt;td&gt;Complete metadata, one H1, descriptive links, responsive WebP images, and language annotations.&lt;/td&gt;
&lt;td&gt;Minor English-copy issues and an opportunity to make the title more audience-specific.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structure and UX&lt;/td&gt;
&lt;td&gt;Short sections, a clear primary CTA, native FAQ disclosures, and no horizontal overflow in the tested viewports.&lt;/td&gt;
&lt;td&gt;The article rail contains 34 cards, which may be more choice than a homepage visitor needs. No conversion or usability study was available.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical SEO&lt;/td&gt;
&lt;td&gt;Google confirms indexing; server-rendered content, self-canonical, valid JSON-LD parsing, HTTPS, and responsive geometry.&lt;/td&gt;
&lt;td&gt;No fresh Lighthouse or URL-level field-performance assessment was run, and this was not a full accessibility or asset audit.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;h2&gt;Top Priority Fixes&lt;/h2&gt;

&lt;p&gt;No critical indexability blocker was found.&lt;/p&gt;

&lt;p&gt;The biggest improvement would be making the existing performance evidence precise and easy to verify. Strong numbers are useful only when visitors understand what was measured.&lt;/p&gt;

&lt;h3&gt;#1 — Qualify the hero’s performance claims&lt;/h3&gt;

&lt;p&gt;🟡 &lt;strong&gt;High&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score impact:&lt;/strong&gt; Potentially +0.5 in E-E-A-T and +0.5 in Content Quality. These are editorial estimates, not projected ranking gains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Our case sites reach a 98/100 median mobile PageSpeed score; the fastest loads in 0.9 seconds.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a nearby evidence link and state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The measurement period.&lt;/li&gt;
&lt;li&gt;The number of measured case sites.&lt;/li&gt;
&lt;li&gt;How the median was calculated.&lt;/li&gt;
&lt;li&gt;Which metric the 0.9-second figure represents.&lt;/li&gt;
&lt;li&gt;Whether the measurement is from a lab test or real-user data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not describe LCP as total page-load time if LCP is the underlying metric.&lt;/p&gt;

&lt;p&gt;A safe nonnumeric alternative, pending verification of those details:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Explore our case studies for monthly PageSpeed measurements and Search Console reporting.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; The audit verified that the numbers appear on the page; it did not independently reproduce them. Clear provenance strengthens trust and avoids confusing a lab performance score with a visitor-experience guarantee.&lt;/p&gt;

&lt;h3&gt;#2 — Update the “latest” HTTP Archive comparison&lt;/h3&gt;

&lt;p&gt;🟡 &lt;strong&gt;High&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score impact:&lt;/strong&gt; Potentially +0.5 in Content Quality and +0.5 in the Trustworthiness subscore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current heading:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We build with Astro — the Core Web Vitals leader in the latest HTTP Archive comparison”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Current explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“In July 2026, 69% of measured mobile Astro sites had good Core Web Vitals, compared with 49% for WordPress and 33% for Next.js.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Verified finding:&lt;/strong&gt; The linked source confirms those July values. However, it now displays August 2026 as the latest period, with Astro at 71%, WordPress at 48%, and Next.js at 35%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suggested heading:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Astro leads this Core Web Vitals comparison”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Suggested explanation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“In HTTP Archive’s August 2026 mobile comparison, 71% of measured Astro origins had good Core Web Vitals, compared with 48% for WordPress and 35% for Next.js.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep the source link. Explain that an origin groups pages under the same protocol, host, and port; this is not a measurement of every page built with a framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; The existing July figures are not fabricated or disproved, but “latest” is stale. Framework-level comparisons also do not prove that a particular website will pass Core Web Vitals.&lt;/p&gt;

&lt;h3&gt;#3 — Make the audience more explicit in the search snippet&lt;/h3&gt;

&lt;p&gt;🟢 &lt;strong&gt;Medium&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score impact:&lt;/strong&gt; Potentially +0.5 in Search Intent Alignment and +0.3 in On-Page SEO.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current title:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Straffe Sites — rock-solid websites that load fast”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Current H1:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“A rock-solid site that loads fast”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Test this title rather than assuming the existing one is underperforming:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Fast websites for small businesses | Straffe Sites”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The proposed title is 50 characters.&lt;/p&gt;

&lt;p&gt;Keep the distinctive H1 if desired, but make the first sentence more explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We design and build fast websites and online stores for independent professionals and small businesses. Seppe is your main contact, supported by a senior team.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; The current meta description already identifies the audience. Bringing that clarity into the title or opening sentence helps qualified visitors recognize the fit.&lt;/p&gt;

&lt;p&gt;GSC does not show a proven CTR problem here. This is a relevance hypothesis, not a fix justified by a statistically reliable CTR deficit.&lt;/p&gt;

&lt;h3&gt;#4 — Give buyers more useful project-planning answers&lt;/h3&gt;

&lt;p&gt;🟢 &lt;strong&gt;Medium&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score impact:&lt;/strong&gt; Potentially +0.5 in Content Quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current timeline answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It varies by project. Because you work directly with the developer, communication is quick. After our first conversation, you’ll get a realistic schedule.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Explain the factors that determine the schedule without inventing a delivery promise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The schedule depends on the number of pages, content readiness, custom integrations, and feedback rounds. After the first conversation, we outline the project milestones and what we need from you.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Review the content-editing answer in the same way: distinguish projects with an editor-friendly CMS from projects where content is maintained through a development workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; Competitors explain publishing autonomy, migrations, and ongoing delivery in more detail. A few concrete answers would help buyers qualify their project without turning the homepage into a technical manual.&lt;/p&gt;



&lt;h2&gt;E-E-A-T Breakdown&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Experience&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;td&gt;Twelve named case-study cards and practical delivery language: “You speak directly with the builder” and “You see the progress as it happens.” The homepage does not itself show detailed before-and-after evidence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expertise&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;td&gt;Correctly identifies LCP, INP, and CLS in the glossary; explains integrations, maintenance, CMS editing, and a staged delivery process. The hero’s “loads in 0.9 seconds” wording lacks metric precision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authoritativeness&lt;/td&gt;
&lt;td&gt;7/10&lt;/td&gt;
&lt;td&gt;Seppe is named, the team page is linked, the server-rendered content states “25+ years of experience,” and relevant case studies and technical articles are linked. Independent reputation and backlinks were not assessed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trustworthiness&lt;/td&gt;
&lt;td&gt;7/10&lt;/td&gt;
&lt;td&gt;HTTPS, direct contact details, legal links, a Google-review link, and an HTTP Archive citation. The review rating and case-performance aggregate were not independently remeasured; the “latest” comparison needs updating.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;YMYL:&lt;/strong&gt; This is not primarily a health, financial-advice, legal-advice, or safety page. Health-related client names in the portfolio do not make the homepage a medical-advice resource.&lt;/p&gt;

&lt;h3&gt;E-E-A-T Gaps to Close&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Attach measurement context directly to the hero’s numerical claims.&lt;/li&gt;
&lt;li&gt;Keep dated comparisons synchronized with their sources, or remove “latest.”&lt;/li&gt;
&lt;li&gt;Consider one short, permission-cleared client quotation with attribution and a relevant case link. Do not invent a testimonial.&lt;/li&gt;
&lt;li&gt;Make ownership, handover, and editing responsibilities clearer where the actual service agreement supports those claims.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The page displays “4.9/5 on Google” and “26 reviews.” These are observed page claims, not independently verified review metrics from this audit.&lt;/p&gt;



&lt;h2&gt;Search Intent Analysis&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Target keyword:&lt;/strong&gt; “Fast websites for small businesses” is an inferred commercial theme, not an established high-volume query for this URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actual leading disclosed GSC query:&lt;/strong&gt; “fast sites,” with 3 clicks and 17 impressions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intent type:&lt;/strong&gt; Commercial service selection and branded navigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content format match:&lt;/strong&gt; &lt;strong&gt;Yes for the commercial theme; uncertain for the ambiguous query “fast sites.”&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;Search-result reality check&lt;/h3&gt;

&lt;p&gt;Two searches were used to avoid judging intent from the homepage alone.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Search&lt;/th&gt;
&lt;th&gt;Observed result sample&lt;/th&gt;
&lt;th&gt;Interpretation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fast website development small business Astro agency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Astro Partner Agencies; Astro’s framework homepage; Lucky Media; Seibert; Opace&lt;/td&gt;
&lt;td&gt;A mixture of agency discovery, commercial service pages, and framework information. The homepage’s service format is compatible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fast sites&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Louisiana’s FastSites program; a governor’s announcement; WAFB coverage; a Catchpoint performance article; Louisiana Illuminator coverage&lt;/td&gt;
&lt;td&gt;Strong ambiguity. This is not a clean web-development purchase query in the returned sample.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are current search-tool results, not a controlled Google rank-tracking study. Geography, device, and personalization were not matched to the historical GSC impressions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; Do not optimize the homepage around “fast sites” simply because it produced three clicks. Preserve the homepage’s brand and service role, and let dedicated service pages own narrower purchase queries.&lt;/p&gt;

&lt;h3&gt;SERP Feature Opportunities&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Optimized?&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Featured snippet&lt;/td&gt;
&lt;td&gt;Not established&lt;/td&gt;
&lt;td&gt;No featured-snippet ownership was confirmed. Do not add a long informational section solely to chase one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;People Also Ask&lt;/td&gt;
&lt;td&gt;Structurally prepared&lt;/td&gt;
&lt;td&gt;Four visible FAQs provide question-and-answer content, but PAA appearances were not reported by the search tool. Improve buyer usefulness rather than promising placement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich results&lt;/td&gt;
&lt;td&gt;Markup present; appearance unconfirmed&lt;/td&gt;
&lt;td&gt;Organization, WebSite, and FAQPage JSON-LD parse successfully. URL Inspection did not return a rich-result assessment. Markup does not guarantee an enhanced result.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image results&lt;/td&gt;
&lt;td&gt;Basic preparation present&lt;/td&gt;
&lt;td&gt;Descriptive alt text and WebP assets exist. Image-search visibility was not measured.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;h2&gt;On-Page SEO Audit&lt;/h2&gt;

&lt;h3&gt;Metadata&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;Current&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Title tag&lt;/td&gt;
&lt;td&gt;“Straffe Sites — rock-solid websites that load fast” — 50 characters&lt;/td&gt;
&lt;td&gt;Good length and clear benefit&lt;/td&gt;
&lt;td&gt;Consider the audience-specific variant above. No demonstrated truncation problem.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta description&lt;/td&gt;
&lt;td&gt;“The Straffe Sites team designs and builds fast websites and online stores for independent professionals and small businesses, with no agency in between.” — 152 characters&lt;/td&gt;
&lt;td&gt;Complete and relevant&lt;/td&gt;
&lt;td&gt;Retain the audience clarity. A stronger invitation to view work is optional, not essential.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H1&lt;/td&gt;
&lt;td&gt;“A rock-solid site that loads fast”&lt;/td&gt;
&lt;td&gt;One H1; clear benefit&lt;/td&gt;
&lt;td&gt;Clarify the service and audience in the adjacent introduction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canonical&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://straffesites.com/en&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Correct&lt;/td&gt;
&lt;td&gt;Google-selected and user-declared canonicals agree.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robots metadata&lt;/td&gt;
&lt;td&gt;No robots meta tag; no X-Robots-Tag in the checked response&lt;/td&gt;
&lt;td&gt;No exclusion detected&lt;/td&gt;
&lt;td&gt;An explicit &lt;code&gt;index,follow&lt;/code&gt; tag is unnecessary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open Graph&lt;/td&gt;
&lt;td&gt;Title, description, URL, image, type, site name, and &lt;code&gt;en_US&lt;/code&gt; locale&lt;/td&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;td&gt;The declared 1200×630 image URL returned HTTP 200; visual preview not reviewed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twitter Card&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;summary_large_image&lt;/code&gt;, title, description, and image&lt;/td&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;td&gt;No urgent change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;HTML &lt;code&gt;lang="en"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Appropriate&lt;/td&gt;
&lt;td&gt;Continue using US English consistently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hreflang&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;nl&lt;/code&gt;, &lt;code&gt;en&lt;/code&gt;, and &lt;code&gt;x-default&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Present on the audited page&lt;/td&gt;
&lt;td&gt;Reciprocal annotations on the Dutch page were outside this page-only audit.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sitewide title and description uniqueness was not tested.&lt;/p&gt;

&lt;h3&gt;Heading Structure&lt;/h3&gt;

&lt;p&gt;The main content has one H1 and a consistent H2 → H3 hierarchy.&lt;/p&gt;

&lt;pre class="highlight text"&gt;&lt;code&gt;H1: A rock-solid site that loads fast
  H2: We build with Astro — the Core Web Vitals leader in the latest HTTP Archive comparison
  H2: One team, everything for your site
    H3: Web development
    H3: Custom features &amp;amp; integrations
    H3: Website maintenance
    H3: Technical partner
    H3: AI design to website
  H2: Work with real personality
    H3: Verhuurwinkel.nl
    H3: Straffe Sites
    H3: Voltti.ai
    H3: Gitaarlessen Boeckx
    H3: 2140 Boxing Training
    H3: Figur
    H3: New Impact
    H3: Roulez Roulez
    H3: sympl
    H3: ZET
    H3: HWV Facilities
    H3: CLW Antwerpen
  H2: How we work together
    H3: Introduction
    H3: Design
    H3: Build
    H3: Launch &amp;amp; support
  H2: No agency. Just a senior team
  H2: Keep reading
    H3: 34 linked article titles
  H2: Frequently asked questions
  H2: Terms
    H3: CMS
    H3: Responsive web design
    H3: Core Web Vitals
    H3: SEO
    H3: Web hosting
    H3: Website maintenance
  H2: Ready for a rock-solid site?
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The article-card H3s cover CMS integrations, websites without a CMS, agency SEO research, lab versus field performance, website costs, semantic markup, AI writing, Search Console, Storyblok, Astro, hiring developers, maintenance, keyword research, and implementation tutorials.&lt;/p&gt;

&lt;p&gt;Navigation and contact-interface headings also exist outside &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;. They are not extra page H1s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assessment:&lt;/strong&gt; Logical structure. The main weakness is not heading hierarchy but emphasis: the long framework-comparison heading is more technical than the buyer-oriented service headings.&lt;/p&gt;

&lt;h3&gt;Internal Links&lt;/h3&gt;

&lt;p&gt;Found &lt;strong&gt;122 same-origin link elements across the document&lt;/strong&gt;, including navigation, repeated destinations, and fragment links. &lt;strong&gt;63 are inside &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are link-element counts, not counts of unique destination pages.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Anchor text or card heading&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discuss your project&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/contact&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clear conversion action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;See recent work ↓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en#werk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Useful on-page navigation; target exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web development&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/website-development&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Relevant; card description adds context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom features &amp;amp; integrations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/web-development&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Descriptive and differentiated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website maintenance&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/website-maintenance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strong service anchor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI design to website&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/ai-design-to-website&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specific service intent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meet the team ↗&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/team&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Useful trust destination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Straffe Sites case card&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/case/straffe-sites&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Useful supporting evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core Web Vitals&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/glossary/core-web-vitals&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Relevant explanation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View all articles&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/en/blog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Appropriate directory action&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The checked contact, service, team, and own-case destinations returned HTTP 200 without a redirect. This was a targeted check, not a full crawl of every link.&lt;/p&gt;

&lt;p&gt;The large link count is not automatically an SEO defect. Much of it comes from the portfolio, article rail, and navigation. A shorter article selection could improve decision-making, but there is no measured evidence here that link count is harming rankings.&lt;/p&gt;

&lt;h3&gt;External Links&lt;/h3&gt;

&lt;p&gt;Two outbound HTTPS link elements were found:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Assessment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google reviews&lt;/td&gt;
&lt;td&gt;Reputation evidence&lt;/td&gt;
&lt;td&gt;Useful direct source; rating and count not independently confirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Archive comparison&lt;/td&gt;
&lt;td&gt;Framework-performance citation&lt;/td&gt;
&lt;td&gt;Source inspected successfully in a browser; July values confirmed and newer August values found&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Email and telephone links are contact actions, not external web citations.&lt;/p&gt;

&lt;h3&gt;Images&lt;/h3&gt;

&lt;p&gt;Found &lt;strong&gt;49 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image group&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Format and attributes&lt;/th&gt;
&lt;th&gt;Assessment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Case-study images&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;WebP, width/height attributes, responsive candidates, lazy loading&lt;/td&gt;
&lt;td&gt;Strong implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Article-card images&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;WebP, 800×450 declared dimensions, lazy loading&lt;/td&gt;
&lt;td&gt;Mostly responsive candidates; four have only an 800w candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reused Seppe portraits&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;WebP with responsive candidates&lt;/td&gt;
&lt;td&gt;Two lazy-loaded; the review avatar uses normal browser loading&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All images have an &lt;code&gt;alt&lt;/code&gt; attribute and declared width and height. &lt;strong&gt;48 are lazy-loaded.&lt;/strong&gt; One review-avatar image has empty alt text; that can be appropriate because the surrounding review link already has an accessible label.&lt;/p&gt;

&lt;p&gt;Representative findings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Current alt text&lt;/th&gt;
&lt;th&gt;Issue or recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Verhuurwinkel case&lt;/td&gt;
&lt;td&gt;“Players in transparent bubble suits running at each other during a game of bubble football”&lt;/td&gt;
&lt;td&gt;Descriptive and relevant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Straffe Sites case&lt;/td&gt;
&lt;td&gt;“…against a light grey background”&lt;/td&gt;
&lt;td&gt;Use “gray” for consistent US English&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Figur case&lt;/td&gt;
&lt;td&gt;“…with grey curly hair…”&lt;/td&gt;
&lt;td&gt;Use “gray”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Impact case&lt;/td&gt;
&lt;td&gt;“Camera operator filming a effects technician…”&lt;/td&gt;
&lt;td&gt;Correct to “an effects technician”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMS article illustration&lt;/td&gt;
&lt;td&gt;“Abstract Straffe Sites illustration of AI agents connecting to a CMS through MCP”&lt;/td&gt;
&lt;td&gt;Understandable; avoid adding keywords merely for SEO&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four article assets with only an 800w candidate were observed at encoded sizes of 7,288, 17,778, 7,446, and 18,088 bytes. Smaller variants might save bytes, but these are not evidence of a major performance problem.&lt;/p&gt;



&lt;h2&gt;Content Quality Assessment&lt;/h2&gt;

&lt;h3&gt;Helpful Content Signals&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Present?&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clear target audience&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;The meta description names independent professionals and small businesses; the hero is broader.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answers the query completely&lt;/td&gt;
&lt;td&gt;Mostly&lt;/td&gt;
&lt;td&gt;Services, portfolio, process, content editing, maintenance, and contact are covered. Project-planning detail is limited.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original value added&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Named projects, a direct-contact model, and linked measurement-oriented case studies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passes the “Last Click” test&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Enough to begin a conversation; less useful for comparing scope, handover, and ongoing responsibilities.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Appropriate depth&lt;/td&gt;
&lt;td&gt;Yes, with a composition caveat&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1,167 words&lt;/strong&gt; in the extracted main-content text, including FAQs, glossary cards, and article-card text.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First-hand knowledge&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Practical delivery descriptions and a substantial linked portfolio.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human-first presentation&lt;/td&gt;
&lt;td&gt;Mostly&lt;/td&gt;
&lt;td&gt;Direct language and clear CTAs; the long article rail serves several audiences at once.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Word-count method:&lt;/strong&gt; Text nodes were joined with spaces; scripts, styles, navigation, and aside elements were excluded. The same method was used for the competitor comparisons. Counts describe content volume, not quality or words visible simultaneously on screen.&lt;/p&gt;

&lt;p&gt;No page-level publication or update date was found in &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; elements or the JSON-LD. A homepage does not require an article-style date, but numerical claims should carry their own date.&lt;/p&gt;

&lt;h3&gt;Content Gaps vs Competitors&lt;/h3&gt;

&lt;p&gt;Two commercial pages from the exploratory search were fetched and inspected. They are comparison references, not proof that Straffe Sites competes against them for every query.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Competitor&lt;/th&gt;
&lt;th&gt;Page type&lt;/th&gt;
&lt;th&gt;Measured main-content words&lt;/th&gt;
&lt;th&gt;Relevant strengths&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.luckymedia.dev/astro-development-agency" rel="noopener noreferrer"&gt;Lucky Media&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Detailed Astro agency service page&lt;/td&gt;
&lt;td&gt;4,327&lt;/td&gt;
&lt;td&gt;Named client quotations, migration examples, publishing autonomy, ownership, retainer support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://astro-en.seibert.group/" rel="noopener noreferrer"&gt;Seibert&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Astro agency landing page&lt;/td&gt;
&lt;td&gt;1,584&lt;/td&gt;
&lt;td&gt;Development, migration, performance optimization, integrations, consulting, and maintenance explanations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Straffe Sites &lt;code&gt;/en&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;General commercial homepage&lt;/td&gt;
&lt;td&gt;1,167&lt;/td&gt;
&lt;td&gt;Broad portfolio, small-team model, direct contact, concise service overview&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Lucky Media H2 topics:&lt;/strong&gt; platform limitations; delivered Astro projects; customer pain points; reasons to choose the agency; promise; process; services; enterprise projects; ongoing support; features; FAQs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seibert H2 topics:&lt;/strong&gt; business impact of performance; publishing workflows; Astro performance; islands; performance demonstration; services; FAQs; UI kit; company scale; partnership; contact.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topic/subtopic&lt;/th&gt;
&lt;th&gt;This page&lt;/th&gt;
&lt;th&gt;Competitor evidence&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who the service is for&lt;/td&gt;
&lt;td&gt;Explicit in metadata; broad in hero&lt;/td&gt;
&lt;td&gt;Both identify more specific buyer scenarios&lt;/td&gt;
&lt;td&gt;Make the opening audience clearer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content-editing autonomy&lt;/td&gt;
&lt;td&gt;One brief FAQ answer&lt;/td&gt;
&lt;td&gt;Both explain publishing workflows in more depth&lt;/td&gt;
&lt;td&gt;Clarify available editing approaches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration concerns&lt;/td&gt;
&lt;td&gt;Not explained in the main service overview&lt;/td&gt;
&lt;td&gt;Both describe migration work&lt;/td&gt;
&lt;td&gt;Add a short answer or link if this is part of the intended offer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ownership and handover&lt;/td&gt;
&lt;td&gt;Not clearly stated in the homepage copy&lt;/td&gt;
&lt;td&gt;Lucky Media explicitly discusses ownership and lock-in&lt;/td&gt;
&lt;td&gt;State only verified contractual terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attributed customer testimony&lt;/td&gt;
&lt;td&gt;Aggregate Google-review badge&lt;/td&gt;
&lt;td&gt;Lucky Media displays named quotes and roles&lt;/td&gt;
&lt;td&gt;Consider one approved, relevant quote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ongoing support&lt;/td&gt;
&lt;td&gt;Present but brief&lt;/td&gt;
&lt;td&gt;Both describe support more specifically&lt;/td&gt;
&lt;td&gt;Explain responsibilities and next steps without publishing prices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not copy competitors’ absolute promises about rankings, “instant” loading, or guaranteed performance. More aggressive wording is not better evidence.&lt;/p&gt;



&lt;h2&gt;Technical SEO&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP response&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Audited URL returned HTTP 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexability&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Indexed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;URL Inspection: &lt;code&gt;PASS&lt;/code&gt;, “Submitted and indexed”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robots.txt&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;HTTP 200; &lt;code&gt;User-agent: *&lt;/code&gt; and &lt;code&gt;Allow: /&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexing directives&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Google reports &lt;code&gt;INDEXING_ALLOWED&lt;/code&gt;; no checked meta/header exclusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canonical&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;User-declared and Google-selected canonical both equal &lt;code&gt;https://straffesites.com/en&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last Google crawl&lt;/td&gt;
&lt;td&gt;Confirmed&lt;/td&gt;
&lt;td&gt;September 5, 2026, 19:09:45 UTC; successful fetch; crawled as mobile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server-rendered content&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Full main content available in fetched HTML, not an empty JavaScript shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile readiness&lt;/td&gt;
&lt;td&gt;Positive bounded evidence&lt;/td&gt;
&lt;td&gt;No horizontal document overflow at 390px; viewport metadata present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop layout&lt;/td&gt;
&lt;td&gt;Positive bounded evidence&lt;/td&gt;
&lt;td&gt;No horizontal document overflow at 1440px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H1 wrapping&lt;/td&gt;
&lt;td&gt;Pass in tested layouts&lt;/td&gt;
&lt;td&gt;Two measured line bands at both 1440px and 390px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary CTA&lt;/td&gt;
&lt;td&gt;Adequate measured size&lt;/td&gt;
&lt;td&gt;Approximately 240×53px on desktop and 350×53px on mobile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intro text&lt;/td&gt;
&lt;td&gt;Readable measured size&lt;/td&gt;
&lt;td&gt;20.8px desktop; 18px mobile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FAQ container&lt;/td&gt;
&lt;td&gt;Contained on mobile&lt;/td&gt;
&lt;td&gt;Heading begins 20px from the left and remains inside the viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema markup&lt;/td&gt;
&lt;td&gt;Basic checks pass&lt;/td&gt;
&lt;td&gt;Organization, WebSite, and FAQPage JSON-LD parse; all four FAQ answers match the page text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich-result status&lt;/td&gt;
&lt;td&gt;Unconfirmed&lt;/td&gt;
&lt;td&gt;Inspection returned no rich-result assessment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google mobile-usability verdict&lt;/td&gt;
&lt;td&gt;Unspecified&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;VERDICT_UNSPECIFIED&lt;/code&gt;; this is not a mobile-usability pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page-speed signals&lt;/td&gt;
&lt;td&gt;Favorable proxies only&lt;/td&gt;
&lt;td&gt;No external stylesheet links; one inline head style block; module scripts and asynchronous analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fonts&lt;/td&gt;
&lt;td&gt;Positive&lt;/td&gt;
&lt;td&gt;Local WOFF2 fonts, two font preloads, and &lt;code&gt;font-display: swap&lt;/code&gt; declarations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DOM size&lt;/td&gt;
&lt;td&gt;Observed, not diagnosed&lt;/td&gt;
&lt;td&gt;968 elements in the inspected document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS/mixed content&lt;/td&gt;
&lt;td&gt;Pass in checked markup&lt;/td&gt;
&lt;td&gt;HTTPS; no HTTP URLs found in inspected &lt;code&gt;src&lt;/code&gt;/&lt;code&gt;href&lt;/code&gt; attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core Web Vitals&lt;/td&gt;
&lt;td&gt;Not measured for this URL&lt;/td&gt;
&lt;td&gt;No fresh field-data or Lighthouse verdict established&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Browser geometry is not a substitute for visual review, assistive-technology testing, or real-user monitoring. No accessibility certification or full performance pass is claimed.&lt;/p&gt;

&lt;p&gt;The page’s 98/100 case aggregate and the HTTP Archive framework percentages are not measurements of this homepage’s current Core Web Vitals.&lt;/p&gt;



&lt;h2&gt;GSC Performance Summary&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Property:&lt;/strong&gt; &lt;code&gt;sc-domain:straffesites.com&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Exact page:&lt;/strong&gt; &lt;code&gt;https://straffesites.com/en&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Period:&lt;/strong&gt; June 13–September 10, 2026&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Data state:&lt;/strong&gt; Final&lt;/p&gt;

&lt;p&gt;Page totals were taken from page-dimension data, not reconstructed from disclosed query rows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clicks, 90 days&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impressions, 90 days&lt;/td&gt;
&lt;td&gt;105&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average CTR&lt;/td&gt;
&lt;td&gt;6.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average position&lt;/td&gt;
&lt;td&gt;5.66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trend&lt;/td&gt;
&lt;td&gt;Too little data for a reliable growth or decline conclusion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Average position is a historical GSC aggregate—not the page’s current fixed rank.&lt;/p&gt;

&lt;h3&gt;Top Ranking Queries&lt;/h3&gt;

&lt;p&gt;All five disclosed query rows for this page are shown below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Avg. position&lt;/th&gt;
&lt;th&gt;Clicks&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;th&gt;CTR&lt;/th&gt;
&lt;th&gt;Expected CTR&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;fast sites&lt;/td&gt;
&lt;td&gt;6.00&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;17.65%&lt;/td&gt;
&lt;td&gt;3–4%, &lt;strong&gt;only if treated as transactional&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;+13.65 to +14.65 percentage points; intent uncertain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fast site&lt;/td&gt;
&lt;td&gt;8.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;1.5–3%, &lt;strong&gt;only if treated as transactional&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;−3 to −1.5 percentage points; sample too small&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sites f&lt;/td&gt;
&lt;td&gt;7.75&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Unclear intent; no suitable benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;straffe&lt;/td&gt;
&lt;td&gt;6.13&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Could be brand-related or unrelated; insufficient evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;websites starting with s&lt;/td&gt;
&lt;td&gt;27.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Outside the supplied benchmark range; poor commercial fit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The benchmark ranges are generic reference bands, not measured expectations for these exact searches. No SERP-feature discount was applied because no relevant feature was confirmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query-data limitation:&lt;/strong&gt; Disclosed queries account for 3 of 7 clicks and 32 of 105 impressions: 42.86% and 30.48%, respectively. They are not the complete search-query picture.&lt;/p&gt;

&lt;p&gt;There is no defensible basis for predicting additional clicks from a title change with this sample.&lt;/p&gt;

&lt;h3&gt;Trend Across Three Nonoverlapping 30-Day Periods&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Period&lt;/th&gt;
&lt;th&gt;Clicks&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;th&gt;CTR&lt;/th&gt;
&lt;th&gt;Avg. position&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;June 13–July 12&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;7.69%&lt;/td&gt;
&lt;td&gt;4.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;July 13–August 11&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;2.22%&lt;/td&gt;
&lt;td&gt;6.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;August 12–September 10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;11.76%&lt;/td&gt;
&lt;td&gt;6.03&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The latest period has more clicks but fewer impressions than the preceding period. With single-digit clicks, describing this as sustained growth would overstate the evidence.&lt;/p&gt;

&lt;p&gt;No algorithm-update attribution is justified.&lt;/p&gt;

&lt;h3&gt;Cannibalization Check&lt;/h3&gt;

&lt;p&gt;The disclosed query &lt;code&gt;fast sites&lt;/code&gt; appeared for:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Page&lt;/th&gt;
&lt;th&gt;Clicks&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;th&gt;Avg. position&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/en&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;6.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;3.47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/blog/laadtijd-website&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is &lt;strong&gt;query overlap, not proven harmful cannibalization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Dutch homepage had the stronger average position, while the English homepage received the disclosed clicks. Language and geography could explain part of that difference.&lt;/p&gt;

&lt;p&gt;Do not consolidate the language homepages or change canonicals on this evidence. Maintain distinct language roles and monitor relevant query/page combinations over a longer period.&lt;/p&gt;



&lt;h2&gt;What to Improve Next&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Correct small English-copy issues.&lt;/strong&gt; Change “a effects technician” to “an effects technician” and standardize “grey” to “gray” in the observed alt text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Evaluate the article rail as a commercial UX choice.&lt;/strong&gt; It contains 34 cards. Consider whether a smaller curated selection would help buyers, while keeping the full blog directory available. This is a usability hypothesis, not a proven ranking fix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preserve the distinction between website builds and custom development.&lt;/strong&gt; The links to &lt;code&gt;/en/website-development&lt;/code&gt; and &lt;code&gt;/en/web-development&lt;/code&gt; serve different roles. Keep their labels differentiated rather than targeting every service page with the same phrase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run a dedicated performance check before changing loading behavior.&lt;/strong&gt; Current HTML and resource signals are favorable, but they do not establish LCP, INP, or CLS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reassess after at least 30 days following any future change.&lt;/strong&gt; Keep a dated baseline, compare matching periods, and focus on qualified commercial queries. Low traffic may require a longer observation window.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;



&lt;h2&gt;Skill Handoffs&lt;/h2&gt;

&lt;p&gt;No follow-up workflow was executed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Handoff&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Metadata optimization&lt;/td&gt;
&lt;td&gt;Develop audience-specific title and description alternatives without assuming the current CTR is deficient&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content writing&lt;/td&gt;
&lt;td&gt;Refine the hero’s evidence wording and expand buyer FAQs using verified service details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword research&lt;/td&gt;
&lt;td&gt;Validate relevant English commercial demand instead of treating “fast sites” as a reliable primary keyword&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured-data review&lt;/td&gt;
&lt;td&gt;Check search-feature eligibility separately from JSON syntax; no missing-schema emergency was found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search Console follow-up&lt;/td&gt;
&lt;td&gt;Reassess this exact URL after at least 30 days, with query/page overlap and low-volume limitations preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; This is an indexed, technically sound commercial homepage with useful portfolio depth. The strongest next step is to make its evidence more precise and its buyer guidance more specific—not to add keywords indiscriminately, expand it into a tutorial, or chase an ambiguous query.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Its scores and recommendations are the agent's assessment, not a Google rating or a promise of results. You can narrow the audit to three priorities without letting it edit the page:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Hermes Agent: choose three audit priorities&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;gpt-6-astra&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Which three changes would you prioritize? Don't edit the page yet.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;I’d prioritize these three changes to &lt;a href="https://straffesites.com/en" rel="noopener noreferrer"&gt;https://straffesites.com/en&lt;/a&gt;, in this order, based on the recorded audit.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the hero’s performance claims verifiable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The line “98/100 median mobile PageSpeed score; the fastest loads in 0.9 seconds” needs context.&lt;/p&gt;

&lt;p&gt;Add a short measurement note and an evidence link stating the period, number of case sites, and calculation method. Identify exactly what the 0.9 seconds measures. If it is lab LCP, call it that rather than total loading time.&lt;/p&gt;

&lt;p&gt;Until those details are verified, use:&lt;br&gt;
   “Explore our case studies for monthly PageSpeed measurements and Search Console reporting.”&lt;/p&gt;

&lt;p&gt;Why first: Performance is the page’s central selling point. Visitors should be able to check the evidence without guessing what the numbers mean. The audit confirmed the claims were present, not that the aggregate was independently reproduced.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Correct the dated HTTP Archive comparison&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The heading says “latest,” but the paragraph uses July 2026 figures. The audit confirmed those July figures and found August data in the linked source.&lt;/p&gt;

&lt;p&gt;Suggested heading:&lt;br&gt;
   “Astro leads this Core Web Vitals comparison”&lt;/p&gt;

&lt;p&gt;Suggested paragraph:&lt;br&gt;
   “In HTTP Archive’s August 2026 mobile comparison, 71% of measured Astro origins had good Core Web Vitals, compared with 48% for WordPress and 35% for Next.js.”&lt;/p&gt;

&lt;p&gt;Keep the source link and briefly explain that these are framework-level results across measured origins, not a guarantee for an individual website. Avoid “latest” unless the wording is maintained with every update.&lt;/p&gt;

&lt;p&gt;Why second: This is a specific, verified freshness issue in a prominent evidence block. Correcting it is more defensible than changing copy based on speculative keyword gains.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the service and audience explicit at the top&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep “A rock-solid site that loads fast” as the H1. It already fits the tested desktop and mobile layouts. Clarify the audience in the opening sentence instead.&lt;/p&gt;

&lt;p&gt;Suggested introduction:&lt;br&gt;
   “We design and build fast websites and online stores for independent professionals and small businesses. Seppe is your main contact, supported by a senior team.”&lt;/p&gt;

&lt;p&gt;Suggested title:&lt;br&gt;
   “Fast websites for small businesses | Straffe Sites”&lt;/p&gt;

&lt;p&gt;Put the qualified measurement note from priority one beside or below this introduction.&lt;/p&gt;

&lt;p&gt;Why third: Prospective clients should immediately recognize what you build and whether it is for them. The existing meta description already does this better than the hero. I would not target “fast sites” aggressively: the recorded search results were ambiguous, and the page’s GSC sample was too small to establish a CTR problem.&lt;/p&gt;

&lt;p&gt;These are editorial priorities, not Google ratings or promises of better rankings. I’d leave the indexed URL, canonical, schema, and broader layout alone for now. No page edits or new audit were performed for this follow-up.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Find a topic using Search Console
&lt;/h3&gt;

&lt;p&gt;Use the planner when you haven't chosen a subject. Ask it to check whether an existing page already answers the question before proposing another article. Then take the topic you prefer to the writing skill.&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Hermes Agent: choose three topics from Search Console&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;gpt-6-astra&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;/notfair-content-planner &lt;a href="https://straffesites.com" rel="noopener noreferrer"&gt;https://straffesites.com&lt;/a&gt;&lt;br&gt;
Suggest three article topics using my Search Console data.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;I recommend three article refreshes rather than three new posts. Each topic already has a relevant page earning impressions; a new URL would split the same search intent.&lt;/p&gt;

&lt;p&gt;Search Console connection verified: sc-domain:straffesites.com&lt;br&gt;
Period: June 13–September 10, 2026, inclusive, finalized Web Search data.&lt;br&gt;
Retrieved: September 13, 2026.&lt;/p&gt;

&lt;p&gt;I checked 3,944 query/page rows, 258 page rows, your repository’s keyword clusters, and the current live articles. The property recorded 150 clicks and 336,530 impressions during this period.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What a website costs: compare quotes without surprises&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Refresh the existing Dutch article:&lt;br&gt;
&lt;a href="https://straffesites.com/blog/wat-kost-een-website-laten-maken" rel="noopener noreferrer"&gt;https://straffesites.com/blog/wat-kost-een-website-laten-maken&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why this topic: It has the largest observed opportunity of these three, with substantial visibility for specific cost questions.&lt;/p&gt;

&lt;p&gt;Query-level evidence for this page:&lt;br&gt;
  “wat kost een website laten maken”&lt;br&gt;
  5,391 impressions · 0 clicks · 0% CTR · average position 14.77&lt;/p&gt;

&lt;p&gt;“website laten maken kosten”&lt;br&gt;
  8,859 impressions · 1 click · 0.011% CTR · average position 17.27&lt;/p&gt;

&lt;p&gt;Whole-page context: 125,547 impressions and 33 clicks.&lt;/p&gt;

&lt;p&gt;Writing angle: Turn the existing quote-comparison advice into a checklist readers can use alongside an actual proposal. Cover included work, revisions, content responsibilities, recurring costs, ownership, and exclusions. Keep the sourced market benchmarks already present.&lt;/p&gt;

&lt;p&gt;Ownership warning: “goedkope website laten maken” appeared on both the budget article (3,548 impressions) and this cost guide (49). Online-store cost queries also overlap: “wat kost een webshop laten maken” received 116 impressions on this general guide and 43 on the dedicated online-store cost article. Keep budget-saving advice and online-store pricing detail on their respective pages. These overlaps are cannibalization signals, not proof of ranking damage.&lt;/p&gt;

&lt;p&gt;Timing: This article received a substantive update on September 11, after the report ends. Review its performance no earlier than October 11 before making another SEO-driven rewrite.&lt;/p&gt;

&lt;p&gt;Writing prompt:&lt;br&gt;
  Refresh the existing Dutch website-cost guide for Belgium and the Netherlands. Develop its quote-comparison section into a practical checklist covering scope, revisions, content, hosting, maintenance, ownership, and exclusions. Preserve sourced market figures with their dates and context. Do not invent prices or publish Straffe Sites rates. Link to the existing budget and online-store cost guides instead of duplicating them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Astro and Prettier: setup checks and formatting fixes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Refresh the existing US English article:&lt;br&gt;
&lt;a href="https://straffesites.com/en/blog/integrate-prettier-astro-tailwindcss" rel="noopener noreferrer"&gt;https://straffesites.com/en/blog/integrate-prettier-astro-tailwindcss&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why this topic: Its main formatting queries already average positions around 6–7, closer to prominent results than the other two recommendations.&lt;/p&gt;

&lt;p&gt;Query-level evidence for this page:&lt;br&gt;
  “astro prettier”&lt;br&gt;
  422 impressions · 1 click · 0.237% CTR · average position 6.88&lt;/p&gt;

&lt;p&gt;“prettier astro”&lt;br&gt;
  376 impressions · 0 clicks · 0% CTR · average position 6.11&lt;/p&gt;

&lt;p&gt;“prettier-plugin-astro”&lt;br&gt;
  218 impressions · 1 click · 0.459% CTR · average position 6.11&lt;/p&gt;

&lt;p&gt;Whole-page context: 4,295 impressions and 14 clicks.&lt;/p&gt;

&lt;p&gt;Writing angle: The article already explains installation, plugin order, and Tailwind stylesheet configuration. Add a short diagnostic path for formatting failures rather than another setup walkthrough: does formatting work from the command line, does the editor use the same configuration, and does class sorting work separately?&lt;/p&gt;

&lt;p&gt;Ownership: Keep formatting on this dedicated guide. The broader Astro starter guide received only 6 impressions for “astro prettier,” compared with 422 here. The historical keyword registry flags overlap, but the current report does not show substantial competition for this query.&lt;/p&gt;

&lt;p&gt;Writing prompt:&lt;br&gt;
  Refresh the existing Astro/Prettier guide in US English. Preserve the configuration and add troubleshooting that separates command-line formatting, editor integration, and Tailwind class sorting. Verify current official documentation and test examples before claiming they work. Keep general Astro/Svelte/Vercel setup in the existing starter guide and link to it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up Google Search Console and fix verification problems&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Refresh the existing US English article:&lt;br&gt;
&lt;a href="https://straffesites.com/en/blog/google-search-console" rel="noopener noreferrer"&gt;https://straffesites.com/en/blog/google-search-console&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why this topic: Google already shows the guide for clear setup intent, but those impressions produce few clicks.&lt;/p&gt;

&lt;p&gt;Query-level evidence for this page:&lt;br&gt;
  “how to set up google search console”&lt;br&gt;
  1,649 impressions · 0 clicks · 0% CTR · average position 14.60&lt;/p&gt;

&lt;p&gt;“google search console setup”&lt;br&gt;
  1,033 impressions · 1 click · 0.097% CTR · average position 14.28&lt;/p&gt;

&lt;p&gt;Whole-page context: 8,677 impressions and 5 clicks.&lt;/p&gt;

&lt;p&gt;Writing angle: Keep the existing three-step setup and practical reporting workflow. A useful next extension is concise verification troubleshooting: choosing the correct property, finding the authoritative DNS provider, placing the TXT record, and distinguishing verification delays from missing report data.&lt;/p&gt;

&lt;p&gt;Ownership and timing: The English guide is the clear owner for these setup queries. Preserve its Dutch counterpart rather than merge languages. The September 9 refresh already improved the reporting instructions and citations; review its results no earlier than October 9 before expanding it again.&lt;/p&gt;

&lt;p&gt;Writing prompt:&lt;br&gt;
  Review the September 9 update before refreshing this US English guide. Preserve the setup steps and page/query workflow. Add concise verification troubleshooting supported by current Google documentation. Separate setup time from DNS waiting time and sitemap discovery from indexing. Do not promise immediate data or ranking gains.&lt;/p&gt;

&lt;p&gt;Evidence limits&lt;/p&gt;

&lt;p&gt;These are observed search opportunities, not traffic forecasts. GSC omits anonymized queries, and average position is not a fixed ranking. Low CTR alone does not establish a title problem.&lt;/p&gt;

&lt;p&gt;Country/page and device/page reports were also retrieved, but their totals did not reconcile with page-only data, including after a device-report recheck. I have not used them to claim audience shares. The proposed troubleshooting sections are editorial extensions of demonstrated broader intent, not independently proven high-volume keywords.&lt;/p&gt;

&lt;p&gt;No articles, publication dates, or shared calendars were created or changed.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Search Console describes searches where your site appeared. It is not a market-wide keyword-volume tool, and sparse data should not become a confident demand forecast. Refreshing an existing article can be a better choice than adding a competing page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improve the draft in the same conversation
&lt;/h3&gt;

&lt;p&gt;Back in the writing conversation, this follow-up revises the Astro-versus-WordPress draft using the developer-focused outline above:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Hermes Agent: revise the draft in the same conversation&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;gpt-6-astra&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;/notfair-content-writer&lt;br&gt;
Revise the draft above. Keep the code examples, cut generic advice,&lt;br&gt;
and make the explanation easier to follow.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;SEO metadata&lt;/p&gt;

&lt;p&gt;Title: Astro vs WordPress: what developers maintain after launch&lt;/p&gt;

&lt;p&gt;Meta description: Astro vs WordPress for developers maintaining small business sites. Compare updates, publishing, integrations, recovery, and the work each setup leaves you.&lt;/p&gt;

&lt;p&gt;Proposed slug: /en/blog/astro-vs-wordpress&lt;br&gt;
Primary keyword: Astro vs WordPress&lt;br&gt;
Secondary topics: Astro maintenance, WordPress maintenance, static-site deployment, headless WordPress.&lt;/p&gt;

&lt;p&gt;The earlier article contained no implementation code examples. I kept the technical explanations and updated its JSON-LD rather than introducing untested code.&lt;/p&gt;



&lt;h1&gt;Astro vs WordPress: what developers maintain after launch&lt;/h1&gt;

&lt;p&gt;A static Astro site can remove a WordPress application and database from the public request path. It cannot remove the developer responsible for publishing changes, fixing forms, and recovering from a bad release. For developers comparing Astro vs WordPress for small business websites, that ongoing work is a better starting point than a framework benchmark.&lt;/p&gt;

&lt;p&gt;Choose static-first Astro when the site mostly publishes content and you can support its editing and deployment workflow. Keep WordPress on the shortlist when its editor or existing plugins already handle the business's requirements. A working WordPress site does not need a migration just because you would prefer to maintain Astro.&lt;/p&gt;

&lt;p&gt;This comparison assumes conventional WordPress with sensible caching, not an intentionally slow installation. On the Astro side, it starts with prebuilt pages. On-demand rendering and headless WordPress add responsibilities of their own.&lt;/p&gt;

&lt;p&gt;Contents&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astro vs WordPress: what becomes your responsibility?&lt;/li&gt;
&lt;li&gt;Follow a content change through to production&lt;/li&gt;
&lt;li&gt;Separate failed updates from broken production&lt;/li&gt;
&lt;li&gt;Trace forms and bookings beyond the frontend&lt;/li&gt;
&lt;li&gt;Headless WordPress leaves you with two systems&lt;/li&gt;
&lt;li&gt;Make the decision against a real maintenance brief&lt;/li&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Astro vs WordPress: what becomes your responsibility?&lt;/h2&gt;

&lt;p&gt;A conventional WordPress installation runs application code and stores content in a database. That does not mean every visitor triggers a fresh database query. WordPress's &lt;a href="https://developer.wordpress.org/advanced-administration/performance/cache/" rel="noopener noreferrer"&gt;caching documentation&lt;/a&gt; describes serving cached static files; uncached requests and dynamic features still need the application.&lt;/p&gt;

&lt;p&gt;Astro &lt;a href="https://docs.astro.build/en/guides/on-demand-rendering/" rel="noopener noreferrer"&gt;prerenders routes by default&lt;/a&gt;. For those routes, the host serves the files produced by the build. You maintain the system that creates and delivers those files rather than a CMS application serving public pages.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Maintenance task&lt;/th&gt;
&lt;th&gt;Static-first Astro&lt;/th&gt;
&lt;th&gt;Conventional WordPress&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Publish content&lt;/td&gt;
&lt;td&gt;Edit files or use a connected CMS, then build and deploy&lt;/td&gt;
&lt;td&gt;Publish through the editor; check cache behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update software&lt;/td&gt;
&lt;td&gt;Framework, packages, build environment, and integrations&lt;/td&gt;
&lt;td&gt;Core, themes, plugins, and the hosting runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recover content&lt;/td&gt;
&lt;td&gt;Restore source content or CMS data; rebuild if needed&lt;/td&gt;
&lt;td&gt;Restore revisions or appropriate database and file backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recover a release&lt;/td&gt;
&lt;td&gt;Restore a known-good deployment or rebuild a reverted version&lt;/td&gt;
&lt;td&gt;Restore compatible application code and account for database changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investigate missing inquiries&lt;/td&gt;
&lt;td&gt;Trace the form endpoint, provider, and delivery&lt;/td&gt;
&lt;td&gt;Trace the plugin or endpoint, provider, and delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a fully static frontend, removing WordPress also removes its core, theme, and plugin update cycle. Repository access, deployment accounts, hosting, and external services remain. A compromised deployment account can replace a static site just as effectively as a compromised application can alter a dynamic one.&lt;/p&gt;

&lt;p&gt;If you add Astro's on-demand rendering, you also need an adapter and runtime. Include those in the maintenance plan. The relevant distinction is which code runs where, not whether the project has an Astro logo.&lt;/p&gt;

&lt;h2&gt;Follow a content change through to production&lt;/h2&gt;

&lt;p&gt;Suppose the client asks you to update a service description and replace its photo. The task is finished when the public page shows the approved content, with the right image and working links.&lt;/p&gt;

&lt;h3&gt;WordPress: check the saved content, then the response&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://wordpress.org/documentation/article/wordpress-block-editor/" rel="noopener noreferrer"&gt;block editor&lt;/a&gt; provides the editing interface. Preview the change, publish it, and check the page as a logged-out visitor. An administrator's view may bypass caching or show content that an ordinary visitor cannot see.&lt;/p&gt;

&lt;p&gt;If the old description remains visible, first confirm the right page was updated and published. Then inspect the page cache and CDN behavior. If only the image is stale, check the image URL and its caching separately. Purging every cache before identifying the stale response can hide the cause.&lt;/p&gt;

&lt;h3&gt;Astro: check the source, build, deployment, and public page&lt;/h3&gt;

&lt;p&gt;With file-based content, the change passes through the repository and build before it reaches production. A CMS adds an editing interface, but a statically built frontend still needs a successful publication path.&lt;/p&gt;

&lt;p&gt;Straffe Sites documents one file-based approach in &lt;a href="https://straffesites.com/en/blog/website-without-cms" rel="noopener noreferrer"&gt;its article about a website without a CMS&lt;/a&gt;: content lives as MDX and JSON in Git, with Astro Content Collections validating configured fields. That is an example of a technical team's workflow, not a requirement for every Astro project.&lt;/p&gt;

&lt;p&gt;When an update is missing, check the stages in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the intended source contain the new content?&lt;/li&gt;
&lt;li&gt;Did the build use that source revision or CMS version?&lt;/li&gt;
&lt;li&gt;Did the build and deployment succeed?&lt;/li&gt;
&lt;li&gt;Does the production domain serve that deployment?&lt;/li&gt;
&lt;li&gt;Is a cached page or asset hiding the change?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A CMS saying "published" answers only the first part of this chain. Give editors a way to tell whether their change reached the website, and assign someone to investigate failures. Otherwise routine publishing problems become unexplained support tickets.&lt;/p&gt;

&lt;h2&gt;Separate failed updates from broken production&lt;/h2&gt;

&lt;p&gt;The following are hypothetical failure scenarios, not reports of tests performed for this article.&lt;/p&gt;

&lt;h3&gt;A WordPress plugin update breaks the form&lt;/h3&gt;

&lt;p&gt;After a plugin update, the contact page still loads, but submissions fail. Start by reproducing a submission with test data. Inspect the browser request and the relevant server or provider logs. Determine whether the failure occurs before the request, at the endpoint, or during onward delivery.&lt;/p&gt;

&lt;p&gt;Before restoring an older plugin version, check whether the update changed stored data and whether the older code can read it. Restoring files alone may not reverse a database migration. Restoring an entire database backup may also discard newer inquiries, bookings, or orders.&lt;/p&gt;

&lt;p&gt;This is why the recovery plan must identify both the code version and the state it expects. WordPress's &lt;a href="https://developer.wordpress.org/advanced-administration/security/hardening/" rel="noopener noreferrer"&gt;security guidance&lt;/a&gt; covers updates and recovery preparation; the separate &lt;a href="https://straffesites.com/en/blog/wordpress-maintenance" rel="noopener noreferrer"&gt;WordPress maintenance guide&lt;/a&gt; provides broader context. For this incident, the useful acceptance test is a submitted inquiry reaching its destination.&lt;/p&gt;

&lt;h3&gt;An Astro dependency update prevents the next deployment&lt;/h3&gt;

&lt;p&gt;An Astro update can fail during dependency installation, validation, or the build. Check the first relevant error and compare the changed dependency versions and runtime requirements. Use the project's lockfile and documented build environment rather than trying unrelated package upgrades.&lt;/p&gt;

&lt;p&gt;A failed build is not necessarily an outage. A deployment system that retains the previous successful release until its replacement is ready can keep serving the old site. A script that deletes or overwrites live files before finishing the build cannot make the same promise. Verify the host's behavior instead of attributing that protection to Astro.&lt;/p&gt;

&lt;p&gt;A successful build can still publish the wrong content or a broken form. Check the deployed page and critical interactions after release. Restoring a previous deployment reverses that deployment's files; it does not automatically reverse changes in an external CMS, form service, or database.&lt;/p&gt;

&lt;p&gt;For either stack, document what "rollback" restores and what it leaves untouched. Recovery should not begin with someone discovering where the backups are.&lt;/p&gt;

&lt;h2&gt;Trace forms and bookings beyond the frontend&lt;/h2&gt;

&lt;p&gt;An HTML form on a static page still needs somewhere to send its data. A WordPress form plugin also depends on processing and delivery beyond the visible form. In both cases, trace the complete path:&lt;/p&gt;

&lt;p&gt;Visitor submission → receiving endpoint → stored record or delivery queue → staff notification or CRM.&lt;/p&gt;

&lt;p&gt;Check each boundary. Did the endpoint accept the submission? Was a record stored? Did the notification arrive? An accepted API request and a delivered email are different events. The confirmation message should accurately describe what the system knows.&lt;/p&gt;

&lt;p&gt;Keep the failure check small and repeatable: submit identifiable test data without private customer details, verify the destination, and remove the test through the agreed operational process. Check invalid input and unavailable-provider behavior as well as the successful path. Never use real charges to test a checkout when the provider supplies a test mode.&lt;/p&gt;

&lt;h3&gt;Do not rebuild working business operations by accident&lt;/h3&gt;

&lt;p&gt;A booking link to an existing scheduling service is a small frontend feature. Replacing a WordPress plugin that manages availability, deposits, and cancellations is a different project. List those behaviors before estimating a migration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://woocommerce.com/" rel="noopener noreferrer"&gt;WooCommerce&lt;/a&gt; is a commerce platform for WordPress. Astro can render a storefront connected to a commerce backend, but it does not supply order management by itself. The new setup still needs someone responsible for payment events, inventory changes, refunds, and failed notifications.&lt;/p&gt;

&lt;p&gt;Retain an established integration when it works unless the replacement solves a specific problem. Moving complexity into custom code or another provider does not make it disappear from your maintenance workload.&lt;/p&gt;

&lt;h2&gt;Headless WordPress leaves you with two systems&lt;/h2&gt;

&lt;p&gt;With headless WordPress, editors retain WordPress while Astro renders the public site. Astro's &lt;a href="https://docs.astro.build/en/guides/cms/wordpress/" rel="noopener noreferrer"&gt;WordPress integration guide&lt;/a&gt; describes fetching content through the WordPress REST API. The broader &lt;a href="https://straffesites.com/en/blog/what-is-a-headless-cms" rel="noopener noreferrer"&gt;headless CMS explanation&lt;/a&gt; covers the separation between editing and presentation.&lt;/p&gt;

&lt;p&gt;For a statically built frontend, you now own the connection between two publication states: content published in WordPress and content deployed by Astro. Define how publication triggers a build, how a failed build is reported, and how an editor previews a draft without exposing it publicly.&lt;/p&gt;

&lt;p&gt;Audit the plugins before committing to this design. A plugin that modifies a WordPress template, inserts frontend scripts, or renders a form will not automatically reproduce that behavior in Astro because the content API is available. Rendering CMS HTML also requires a clear trust boundary; inserting a string as HTML is not a sanitization step.&lt;/p&gt;

&lt;p&gt;Headless WordPress is justified when preserving the editorial workflow has value and a separate frontend meets a concrete requirement. If the conventional site already works, adding a second deployment system solely to change frameworks is hard to justify.&lt;/p&gt;

&lt;h2&gt;Make the decision against a real maintenance brief&lt;/h2&gt;

&lt;p&gt;Consider a hypothetical electrical contractor with eight service pages, a project gallery, a contact form, and an external scheduling service. The developer handles occasional content changes.&lt;/p&gt;

&lt;p&gt;Static Astro fits that brief: prebuilt pages, a defined form endpoint, and a link to the existing scheduler. The maintenance agreement still needs a response time for urgent content corrections and a backup maintainer who can publish them.&lt;/p&gt;

&lt;p&gt;If an office manager must publish projects independently every week, include an editor in the Astro proposal and compare the resulting setup with WordPress. If bookings and payments already depend on a working WordPress plugin, keeping WordPress may avoid a substantial integration rewrite.&lt;/p&gt;

&lt;h3&gt;Test the actual site before using speed to justify a move&lt;/h3&gt;

&lt;p&gt;Astro's &lt;a href="https://docs.astro.build/en/concepts/islands/" rel="noopener noreferrer"&gt;islands architecture&lt;/a&gt; lets you add interactive components without hydrating the entire page. That is useful control over browser work, but a booking embed, large image, or added script can still dominate performance.&lt;/p&gt;

&lt;p&gt;Compare representative pages with the same required content and integrations. Record lab conditions, check mobile behavior, and use available real-user data as a separate source. Do not compare an empty Astro demo with a full production WordPress page.&lt;/p&gt;

&lt;p&gt;Google states that &lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;good Core Web Vitals do not guarantee top rankings&lt;/a&gt;. A platform change needs its own operational justification. If you migrate, preserve useful URLs and verify redirects, metadata, internal links, and indexability rather than treating the rebuild as an automatic SEO improvement.&lt;/p&gt;

&lt;p&gt;Before recommending a stack, put these answers in the handover document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who publishes routine changes, and who handles urgent ones?&lt;/li&gt;
&lt;li&gt;Which endpoint receives inquiries, and how is delivery checked?&lt;/li&gt;
&lt;li&gt;What does the update process test before and after release?&lt;/li&gt;
&lt;li&gt;What survives a failed build, and what does rollback restore?&lt;/li&gt;
&lt;li&gt;Can another developer publish and recover the site using the documentation?&lt;/li&gt;
&lt;li&gt;Can the current site's problems be fixed without migration?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose Astro when its publishing and delivery model removes work you would otherwise maintain. Choose WordPress when its editor and integrations already do that work well. Keep the existing site if a rebuild cannot explain what becomes simpler afterward.&lt;/p&gt;

&lt;h2&gt;Frequently asked questions&lt;/h2&gt;

&lt;h3&gt;Does a static Astro site still need dependency updates?&lt;/h3&gt;

&lt;p&gt;Yes. Static delivery removes the application runtime from prebuilt page requests, but the framework, packages, build environment, and integrations still need maintenance. Prioritize updates according to the affected component and its exposure.&lt;/p&gt;

&lt;h3&gt;Can a failed Astro build take the live website down?&lt;/h3&gt;

&lt;p&gt;That depends on deployment behavior, not Astro alone. A system that keeps serving the previous successful release while a replacement builds can preserve availability. A deployment script that removes live files first may not. Verify your setup.&lt;/p&gt;

&lt;h3&gt;Does managed WordPress hosting remove the developer's maintenance work?&lt;/h3&gt;

&lt;p&gt;No. The host may handle infrastructure, backups, or some updates. The developer still needs to know what the agreement covers and who tests custom code, plugins, forms, and business workflows after changes.&lt;/p&gt;

&lt;h3&gt;When is headless WordPress worth maintaining?&lt;/h3&gt;

&lt;p&gt;When retaining WordPress's editorial workflow and using a separate frontend solve concrete requirements. Account for both systems, including previews, API access, publication triggers, failed builds, and plugin behavior that must be recreated in the frontend.&lt;/p&gt;



&lt;p&gt;Accompanying publication notes&lt;/p&gt;

&lt;p&gt;Structured data&lt;/p&gt;

&lt;p&gt;This replaces the previous draft's headline, description, and FAQ content. JSON parsing and exact FAQ-text matching were checked.&lt;/p&gt;

&lt;p&gt;Publication-specific fields remain deliberately absent: author, dates, canonical URL, and image must reflect the actual approved publication. Image concepts are not published assets. FAQPage describes the visible questions; it does not promise Google FAQ rich results, which Google's previously verified documentation says are no longer shown.&lt;/p&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;"@context"&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://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@graph"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BlogPosting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Astro vs WordPress: what developers maintain after launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Astro vs WordPress for developers maintaining small business sites. Compare updates, publishing, integrations, recovery, and the work each setup leaves you."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"about"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Astro"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WordPress"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAQPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mainEntity"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does a static Astro site still need dependency updates?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes. Static delivery removes the application runtime from prebuilt page requests, but the framework, packages, build environment, and integrations still need maintenance. Prioritize updates according to the affected component and its exposure."&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;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can a failed Astro build take the live website down?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"That depends on deployment behavior, not Astro alone. A system that keeps serving the previous successful release while a replacement builds can preserve availability. A deployment script that removes live files first may not. Verify your setup."&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;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does managed WordPress hosting remove the developer's maintenance work?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No. The host may handle infrastructure, backups, or some updates. The developer still needs to know what the agreement covers and who tests custom code, plugins, forms, and business workflows after changes."&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;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"When is headless WordPress worth maintaining?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"When retaining WordPress's editorial workflow and using a separate frontend solve concrete requirements. Account for both systems, including previews, API access, publication triggers, failed builds, and plugin behavior that must be recreated in the frontend."&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;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;p&gt;Image concepts&lt;/p&gt;

&lt;p&gt;Concepts only; no images generated or inserted.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Featured illustration, after the opening paragraph&lt;br&gt;
Filename: astro-wordpress-maintenance.webp&lt;br&gt;
Alt: “Static publishing and WordPress application maintenance shown as separate workflows with shared integrations.”&lt;br&gt;
Concept prompt: Wide 16:9 editorial 3D diagram with two equally weighted workflows, connected to shared form and hosting services. Warm beige #E9E6DF, charcoal modules, paper panels, lime connectors. Avoid winner symbols, invented metrics, logos, and fake dashboard screenshots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publishing-flow diagram, under “Follow a content change through to production”&lt;br&gt;
Filename: website-content-publication-paths.webp&lt;br&gt;
Alt: “WordPress publication and Astro build-and-deploy paths, each ending with a public-page check.”&lt;br&gt;
Concept prompt: Two horizontal process lanes. WordPress: edit, publish, cache, public page. Astro: source change, build, deployment, public page. Mark each diagnostic checkpoint. Use beige, charcoal, and lime; add labels during design production. Avoid timing claims and implied test results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recovery comparison, under “Separate failed updates from broken production”&lt;br&gt;
Filename: website-release-recovery-boundaries.webp&lt;br&gt;
Alt: “Code, deployed files, and external data shown as separate recovery boundaries.”&lt;br&gt;
Concept prompt: A clear 4:3 technical diagram separating application files, static deployment artifacts, and persistent data. Show rollback arrows limited to their actual boundary. Neutral beige background, charcoal outlines, lime highlights. Avoid suggesting that restoring code also restores external data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inquiry-delivery diagram, under “Trace forms and bookings beyond the frontend”&lt;br&gt;
Filename: website-form-delivery-checkpoints.webp&lt;br&gt;
Alt: “A form submission passing through an endpoint, storage or queue, and notification destination.”&lt;br&gt;
Concept prompt: Wide process diagram showing submission, endpoint acceptance, storage or queue, and notification or CRM as distinct checkpoints. Use simple labeled boxes, beige, charcoal, and lime. Avoid customer information, provider branding, and success badges presented as measured evidence.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Internal linking&lt;/p&gt;

&lt;p&gt;The revision retains contextual links to the existing no-CMS, WordPress maintenance, and headless CMS articles. Those pages remain the detailed references; this article owns the developer-maintenance comparison.&lt;/p&gt;

&lt;p&gt;Suggested incoming links remain editorial recommendations only:&lt;br&gt;
- From the no-CMS article: “maintaining Astro versus WordPress.”&lt;br&gt;
- From the WordPress maintenance article: “whether a platform migration reduces maintenance.”&lt;br&gt;
- From the Astro-versus-Next.js article: “Astro versus WordPress after launch.”&lt;/p&gt;

&lt;p&gt;Before publication&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm author, dates, route, and canonical destination.&lt;/li&gt;
&lt;li&gt;Produce the approved imagery and populate image metadata with real asset URLs.&lt;/li&gt;
&lt;li&gt;Integrate the revised schema without duplicating existing template output.&lt;/li&gt;
&lt;li&gt;Check rendered headings, links, table behavior, and mobile layout.&lt;/li&gt;
&lt;li&gt;Keep the failure scenarios labeled hypothetical; no execution evidence has been added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Change notes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shifted the explanation from owner-facing platform selection to developer responsibilities.&lt;/li&gt;
&lt;li&gt;Replaced broad advice with publishing checkpoints and specific failure scenarios.&lt;/li&gt;
&lt;li&gt;Clarified cache behavior, failed builds versus outages, and code rollback versus data recovery.&lt;/li&gt;
&lt;li&gt;Kept the small-business example, but tied each requirement change to maintenance work.&lt;/li&gt;
&lt;li&gt;Reused the already verified sources without rerunning the earlier broad research.&lt;/li&gt;
&lt;li&gt;Updated the metadata and FAQ schema. No website or external content was changed.&lt;/li&gt;
&lt;/ul&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;You don't need to repeat the URL, audience, and full brief when they are already in the conversation. Point to the part you want changed and say what would make it more useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the draft before you publish
&lt;/h2&gt;

&lt;p&gt;Read the article, check its sources, and run the code examples where safe. This next message continues the same writing conversation and asks Hermes to review the revised draft, fix what it can, and report what it could not verify:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Hermes Agent: check facts, links, and code before publishing&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;gpt-6-astra&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Check this draft's facts, links, and code examples. Fix the issues&lt;br&gt;
and tell me what you couldn't verify. Don't publish it.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;The draft is broadly accurate. I corrected seven passages, checked the links against their actual destinations, and validated the JSON-LD. Nothing was published or changed on the website.&lt;/p&gt;

&lt;p&gt;Checked on September 13, 2026.&lt;/p&gt;

&lt;p&gt;Review results&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All 11 original article links returned HTTP 200 and led to relevant pages.&lt;/li&gt;
&lt;li&gt;Both sources added during the corrections also returned HTTP 200. The corrected article has 13 working links.&lt;/li&gt;
&lt;li&gt;The seven table-of-contents entries match the headings using conventional Markdown slug rules. Final rendered anchors remain untested.&lt;/li&gt;
&lt;li&gt;The JSON-LD parses successfully, has no duplicate keys, and matches all four visible FAQ questions and answers.&lt;/li&gt;
&lt;li&gt;The article has no executable Astro, JavaScript, PHP, or shell examples. Its only accompanying code block is JSON-LD, which is data—not an implementation to run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Corrections&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Narrow the WordPress caching claim&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;“Uncached requests … still need the application” was too broad. An uncached image or stylesheet can be served without running WordPress.&lt;/p&gt;

&lt;p&gt;Corrected passage:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;A conventional WordPress installation runs application code and stores content in a database. That does not mean every visitor triggers a fresh database query. WordPress's caching documentation describes serving cached static files; requests that generate a page or process a submission through WordPress still need the application. Static assets such as images and stylesheets can be served directly by the host or CDN.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Source:&lt;br&gt;
&lt;a href="https://developer.wordpress.org/advanced-administration/performance/cache/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/advanced-administration/performance/cache/&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Distinguish removing WordPress from replacing its frontend&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The original wording could suggest that a static frontend eliminates WordPress maintenance even when WordPress remains the CMS. It also made an unnecessary comparison between different kinds of compromise.&lt;/p&gt;

&lt;p&gt;Corrected passage:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;Retiring the WordPress installation removes its core, theme, and plugin update cycle. Replacing only the frontend does not: headless WordPress still needs maintenance. Repository access, deployment accounts, hosting, and external services also remain. Someone with permission to publish a deployment can change the public site, even when its pages are static.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Sources:&lt;br&gt;
&lt;a href="https://docs.astro.build/en/guides/cms/wordpress/" rel="noopener noreferrer"&gt;https://docs.astro.build/en/guides/cms/wordpress/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.wordpress.org/advanced-administration/security/hardening/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/advanced-administration/security/hardening/&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make Git a workflow choice, not an Astro requirement&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;File-based content does not inherently require a repository.&lt;/p&gt;

&lt;p&gt;Corrected passage:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;In a Git-based workflow, a file change passes through the repository and build before it reaches production. Git is a workflow choice, not an Astro requirement. A CMS adds an editing interface, but a statically built frontend still needs a successful publication path.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The Straffe Sites example remains appropriate: its public no-CMS article describes MDX and JSON content in Git. That verifies the attributed example, not a requirement for every Astro site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://straffesites.com/en/blog/website-without-cms" rel="noopener noreferrer"&gt;https://straffesites.com/en/blog/website-without-cms&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Strengthen the plugin rollback instructions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The draft correctly separated plugin files from database state. It needed two precautions: preserve the current state before recovery, and check whether downgrading would restore a known vulnerability.&lt;/p&gt;

&lt;p&gt;Corrected passage:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;Before restoring an older plugin version, preserve the current files and database. Check whether the update changed stored data, whether the older code can read it, and whether rolling back would reintroduce a known vulnerability. Test recovery on an isolated staging copy where possible. Restoring plugin files does not undo database changes. Restoring an older database can discard inquiries, bookings, or orders recorded after that backup.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is recovery guidance, not a claim that a particular plugin update or database migration was tested.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cite backup documentation directly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The general hardening guide was relevant, but the backup documentation supports the files-versus-database explanation more directly.&lt;/p&gt;

&lt;p&gt;Corrected passage:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;WordPress's backup documentation explains why a typical full-site restore needs both files and the database. Its security guidance covers keeping software current. The separate WordPress maintenance guide provides broader context.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;New direct source:&lt;br&gt;
&lt;a href="https://developer.wordpress.org/advanced-administration/security/backup/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/advanced-administration/security/backup/&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clarify deployment rollback versus rebuilding an old commit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;“Reverses that deployment’s files” was ambiguous. Serving an earlier build and rebuilding earlier source are different recovery operations.&lt;/p&gt;

&lt;p&gt;Corrected passage:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;Restoring a known-good static deployment puts its built files back into service. It does not automatically restore external CMS content, form-service data, database state, or separately managed hosting settings. Rebuilding an old commit is a different operation: external content, dependency resolution, or build settings may have changed since the original release.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The existing warning about failed builds stays: retaining the previous live release is deployment-system behavior, not an Astro guarantee. I did not test a hosting provider’s rollback behavior.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the HTML-rendering warning specific&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The draft’s sanitization warning was correct but abstract. Astro documents the relevant behavior explicitly.&lt;/p&gt;

&lt;p&gt;Corrected passage:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;Rendering CMS HTML also requires a clear trust boundary. Astro's set:html directive inserts HTML without automatically escaping it. Use it only for trusted HTML or content sanitized with an appropriate allowlist. Render untrusted plain text through Astro's normal escaped expressions instead.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Source, including its checked fragment:&lt;br&gt;
&lt;a href="https://docs.astro.build/en/reference/directives-reference/#sethtml" rel="noopener noreferrer"&gt;https://docs.astro.build/en/reference/directives-reference/#sethtml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No sanitizer implementation is included or claimed to have been tested.&lt;/p&gt;

&lt;p&gt;Facts that can stay&lt;/p&gt;

&lt;p&gt;The official sources support these central claims:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Astro prerenders routes by default. On-demand rendering requires an adapter and a suitable runtime.&lt;br&gt;
&lt;a href="https://docs.astro.build/en/guides/on-demand-rendering/" rel="noopener noreferrer"&gt;https://docs.astro.build/en/guides/on-demand-rendering/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Astro islands allow selected components to become interactive without hydrating the whole page. That does not guarantee a fast site once images, embeds, and scripts are included.&lt;br&gt;
&lt;a href="https://docs.astro.build/en/concepts/islands/" rel="noopener noreferrer"&gt;https://docs.astro.build/en/concepts/islands/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Astro can fetch WordPress content through the REST API. Making content available through an API does not automatically reproduce a plugin’s frontend behavior.&lt;br&gt;
&lt;a href="https://docs.astro.build/en/guides/cms/wordpress/" rel="noopener noreferrer"&gt;https://docs.astro.build/en/guides/cms/wordpress/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WordPress provides a block editor, and WooCommerce provides commerce functionality for WordPress. Astro itself is not an order-management backend.&lt;br&gt;
&lt;a href="https://wordpress.org/documentation/article/wordpress-block-editor/" rel="noopener noreferrer"&gt;https://wordpress.org/documentation/article/wordpress-block-editor/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://woocommerce.com/" rel="noopener noreferrer"&gt;https://woocommerce.com/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Good Core Web Vitals do not guarantee top Google rankings. The draft correctly avoids presenting a framework migration as an automatic SEO improvement.&lt;br&gt;
&lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/appearance/page-experience&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The remaining internal article links also worked. I checked their relevance to the passages that cite them; I did not audit every claim on those linked pages.&lt;/p&gt;

&lt;p&gt;JSON-LD and publication notes&lt;/p&gt;

&lt;p&gt;No JSON syntax correction was needed. The existing BlogPosting and FAQPage graph can remain as draft data.&lt;/p&gt;

&lt;p&gt;The distinction that matters:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;This JSON-LD has passed syntax and FAQ-content checks. It has not been validated inside a rendered page or submitted to Google’s Rich Results Test or Schema.org’s validator. Add the approved author, publication dates, canonical details, and real image assets before publication; do not invent values to complete the draft.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The FAQ rich-result statement is also supported by current Google documentation. It should now include the direct citation rather than refer vaguely to “previously verified documentation.”&lt;/p&gt;

&lt;p&gt;Corrected note:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;FAQPage remains a Schema.org type, but Google states that FAQ rich results are no longer shown in Search. Keeping this graph is optional for describing the visible FAQ; it is not a recommendation for gaining a Google rich result.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Sources:&lt;br&gt;
&lt;a href="https://schema.org/FAQPage" rel="noopener noreferrer"&gt;https://schema.org/FAQPage&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developers.google.com/search/updates#removing-faq-rich-result" rel="noopener noreferrer"&gt;https://developers.google.com/search/updates#removing-faq-rich-result&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Google changelog fragment exists and was checked.&lt;/p&gt;

&lt;p&gt;What I could not verify&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actual failure recovery. I did not update plugins, install dependencies, restore databases, run builds, or deploy anything.&lt;/li&gt;
&lt;li&gt;Forms, bookings, payments, or email delivery. I submitted no test records and made no payment requests.&lt;/li&gt;
&lt;li&gt;Provider-specific guarantees. No particular hosting contract, backup retention policy, atomic deployment behavior, or rollback process was tested.&lt;/li&gt;
&lt;li&gt;Performance differences. No Astro-versus-WordPress benchmark was run. The draft makes no measured performance comparison.&lt;/li&gt;
&lt;li&gt;Final presentation. The unpublished draft has not been rendered, so mobile layout, table overflow, accessibility, actual heading IDs, and embedded JSON-LD remain unchecked.&lt;/li&gt;
&lt;li&gt;Image assets. They remain concepts. There are no generated images or asset URLs to validate.&lt;/li&gt;
&lt;li&gt;Publication metadata. Author attribution, publication dates, canonical destination, and image metadata still require approval.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hypothetical scenarios remain labeled as hypothetical. The corrected draft preserves the original argument without presenting documentation checks as hands-on testing.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If Hermes can access your website repository, start it from that directory and ask it to save the approved draft using the project's existing format. A website URL alone does not give it filesystem or CMS access. My site uses &lt;a href="https://straffesites.com/en/blog/website-without-cms" rel="noopener noreferrer"&gt;MDX and JSON in Git with Astro&lt;/a&gt;; other sites can use their CMS's draft mode.&lt;/p&gt;

&lt;p&gt;Keep the publication decision yourself. Record the live URL, publication date, and any pre-change measurements. After at least 30 days, ask Hermes to compare the article's Search Console performance over comparable periods. For a new URL, track its first results rather than inventing a prepublication baseline. That's the monthly review pattern behind my &lt;a href="https://straffesites.com/en/blog/automating-seo-with-ai" rel="noopener noreferrer"&gt;SEO automation workflow&lt;/a&gt;, not a promise that every article will improve rankings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting before you blame the model
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The slash command is missing:&lt;/strong&gt; check that the adapted skill is installed. Restart the session and check the exact command name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The skill still requests a hosted connector:&lt;/strong&gt; ask Hermes to check the adaptation and its shared references. It should use the gcloud connection you approved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google refuses access:&lt;/strong&gt; ask Hermes to verify the actual identity and property permissions. Don't paste credentials into chat or grant broader rights blindly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The article feels generic:&lt;/strong&gt; give it a specific question, your own example, or a technical opinion you can defend. Another writing pass cannot create missing experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with one useful task. The skills contain the detailed procedures; your chat messages can stay short.&lt;/p&gt;




&lt;p&gt;For more on choosing a topic, drafting, and reviewing your work, see my &lt;a href="https://straffesites.com/en/blog/optimize-your-blog-with-chatgpt" rel="noopener noreferrer"&gt;AI writing workflow on Straffe Sites&lt;/a&gt;. The installation commands follow the linked official documentation. The installation and setup prompts are examples for your own environment, not transcripts of freshly completed installations or guaranteed results. The Agent Session blocks from “Turn the setup into an article” onward are recorded Hermes exchanges.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>python</category>
      <category>seo</category>
    </item>
    <item>
      <title>Set up Astro with Svelte, Tailwind CSS, Vercel, Prettier, and TypeScript</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:32:10 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/set-up-astro-with-svelte-tailwind-css-vercel-prettier-and-typescript-7m0</link>
      <guid>https://dev.to/seppegadeyne/set-up-astro-with-svelte-tailwind-css-vercel-prettier-and-typescript-7m0</guid>
      <description>&lt;p&gt;I use Astro with Svelte for interactive components, Tailwind CSS for styling, and Prettier to keep formatting consistent. This guide walks through the project setup, strict TypeScript checks, and a static deployment to Vercel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Before you begin, make sure you have the following on your machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 22.12.0 or higher (odd-numbered releases are not supported)&lt;/li&gt;
&lt;li&gt;Visual Studio Code with these extensions: astro-vscode, prettier-vscode, svelte-vscode, and tailwindcss-intellisense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Astro needs no global CLI and no Docker container to get going.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Astro project setup
&lt;/h2&gt;

&lt;p&gt;Create a new Astro project with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro asks where to create the project and which starter to use. Choose a minimal template unless you already need a themed starter, and keep the strict TypeScript configuration shown below. That gives you a clean setup without demo files to remove later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Svelte, Tailwind CSS, Prettier, and type checks
&lt;/h2&gt;

&lt;p&gt;From inside the newly created project folder, add the integrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx astro add svelte
npx astro add tailwind
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @astrojs/check typescript prettier prettier-plugin-astro prettier-plugin-svelte prettier-plugin-tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailwind's integration has changed. The &lt;code&gt;npx astro add tailwind&lt;/code&gt; command now installs the Tailwind Vite plugin (&lt;code&gt;@tailwindcss/vite&lt;/code&gt;), which is the recommended path for Tailwind 4. The integration commands update &lt;code&gt;astro.config.mjs&lt;/code&gt; where needed, so you rarely have to configure them by hand. Accept the defaults when prompted.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;src/styles/global.css&lt;/code&gt; with &lt;code&gt;@import "tailwindcss";&lt;/code&gt; if the command has not already created it. Import that stylesheet in your shared Astro layout frontmatter with &lt;code&gt;import '../styles/global.css'&lt;/code&gt; (adjust the relative path). The Vite plugin alone does not apply styles to a page.&lt;/p&gt;

&lt;p&gt;Why this stack? Astro renders pages to static HTML by default and only ships JavaScript where an island needs it. That helps keep a &lt;a href="https://straffesites.com/en/glossary/static-website" rel="noopener noreferrer"&gt;static website&lt;/a&gt; fast. I use Svelte 5 for interactive parts because it has a small runtime, runes-based reactivity, and a comfortable component model. Tailwind CSS provides utility classes and can encode the tokens from a &lt;a href="https://straffesites.com/en/glossary/design-system" rel="noopener noreferrer"&gt;design system&lt;/a&gt;, keeping the styling predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Prettier
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;.prettierrc&lt;/code&gt; file in the root of your project:&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;"useTabs"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"singleQuote"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trailingComma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"semi"&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="nl"&gt;"printWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tailwindStylesheet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/styles/global.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&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="s2"&gt;"prettier-plugin-astro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier-plugin-svelte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier-plugin-tailwindcss"&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;Create a &lt;code&gt;.prettierignore&lt;/code&gt; too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/**
vercel.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter in 2026. First, &lt;code&gt;pluginSearchDirs&lt;/code&gt; is gone. Prettier 3 no longer discovers plugins implicitly, so you must list them in &lt;code&gt;plugins&lt;/code&gt;. Second, &lt;code&gt;prettier-plugin-tailwindcss&lt;/code&gt; sorts classes for both Tailwind 3 and 4 configurations. With Tailwind 4, &lt;code&gt;tailwindStylesheet&lt;/code&gt; is required: point it at your actual CSS entry file, here &lt;code&gt;src/styles/global.css&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Astro
&lt;/h2&gt;

&lt;p&gt;A typical &lt;code&gt;astro.config.mjs&lt;/code&gt; for this stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&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="s1"&gt;astro/config&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="nx"&gt;svelte&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@astrojs/svelte&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="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://yourdomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;svelte&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;vite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;trailingSlash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro's default output is static, and a fully static site can deploy to Vercel without an adapter. The platform serves the exported HTML and assets from its CDN without per-request rendering. Run &lt;code&gt;npx astro add vercel&lt;/code&gt; only when you need on-demand routes or Vercel-specific services such as image optimization; current versions import the adapter from &lt;code&gt;@astrojs/vercel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;astro check&lt;/code&gt; command type-checks &lt;code&gt;.astro&lt;/code&gt; files. It requires &lt;code&gt;@astrojs/check&lt;/code&gt; and TypeScript, which the install command above adds. Run it in CI to catch type errors before they are merged or deployed:&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;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro check &amp;amp;&amp;amp; astro build"&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;h2&gt;
  
  
  Configuring TypeScript
&lt;/h2&gt;

&lt;p&gt;Edit &lt;code&gt;tsconfig.json&lt;/code&gt;:&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;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro/tsconfigs/strict"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&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="s2"&gt;".astro/types.d.ts"&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="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&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="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&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;"paths"&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;"@/*"&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="s2"&gt;"./src/*"&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;p&gt;The &lt;code&gt;paths&lt;/code&gt; setting gives you path aliases: import components as &lt;code&gt;@/components/Button.astro&lt;/code&gt; instead of &lt;code&gt;../../components/Button.astro&lt;/code&gt;. My site uses the same trick with &lt;code&gt;@blocks/*&lt;/code&gt; for its block registry.&lt;/p&gt;

&lt;p&gt;The old advice to declare a &lt;code&gt;*.astro&lt;/code&gt; module shim in &lt;code&gt;src/env.d.ts&lt;/code&gt; is obsolete. Since version 3, Astro has generated typed references for &lt;code&gt;.astro&lt;/code&gt; imports automatically. If you still have such a declaration lying around, you can delete it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Vercel
&lt;/h2&gt;

&lt;p&gt;A static deployment needs little configuration after you connect the repository in Vercel. Here is a minimal &lt;code&gt;vercel.json&lt;/code&gt; for the project root:&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;"regions"&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="s2"&gt;"fra1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cleanUrls"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trailingSlash"&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="nl"&gt;"redirects"&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="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/old-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/new-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"permanent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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;&lt;code&gt;trailingSlash: false&lt;/code&gt; makes Vercel redirect &lt;code&gt;/old-slug/&lt;/code&gt; to &lt;code&gt;/old-slug&lt;/code&gt; with a 308, which keeps your URLs canonical. When you migrate slugs, account for both trailing-slash variants so every old incoming link reaches the intended page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the project
&lt;/h2&gt;

&lt;p&gt;Start the development server with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the project behaves oddly after an Astro upgrade, reinstall the dependencies before rewriting your configuration. Stale dependency state is often the culprit.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://straffesites.com/en/blog/astro-setup-svelte-tailwindcss-vercel-prettier-typescript" rel="noopener noreferrer"&gt;Straffe Sites&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>svelte</category>
      <category>tailwindcss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Integrate Prettier with Astro and Tailwind CSS</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Mon, 07 Sep 2026 08:53:55 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/integrate-prettier-with-astro-and-tailwind-css-58ig</link>
      <guid>https://dev.to/seppegadeyne/integrate-prettier-with-astro-and-tailwind-css-58ig</guid>
      <description>&lt;p&gt;Prettier handles indentation, quotes, and line breaks. With the right plugins, it also formats &lt;code&gt;.astro&lt;/code&gt; files and sorts Tailwind utility classes. This guide sets up both for an &lt;a href="https://straffesites.com/en/glossary/astro" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; project using Prettier 3 and Tailwind CSS 4.&lt;/p&gt;

&lt;p&gt;There are two details to get right: Prettier 3 requires explicit plugin loading, and the Tailwind plugin needs the CSS entry point for your Tailwind 4 configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Use a current Node.js LTS release supported by your Astro version, with npm installed. If you use VS Code, install these extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=astro-build.astro-vscode" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; (&lt;code&gt;astro-build.astro-vscode&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; (&lt;code&gt;esbenp.prettier-vscode&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Optional: &lt;a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss" rel="noopener noreferrer"&gt;Tailwind CSS IntelliSense&lt;/a&gt; for class suggestions; it is not required for formatting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do not have an Astro project yet, create one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose a starter template and install its dependencies when prompted. Run the remaining commands from the project folder created by the wizard. This guide assumes Astro 5.2 or newer, where &lt;code&gt;astro add tailwind&lt;/code&gt; installs the Tailwind 4 Vite plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Tailwind CSS and Prettier
&lt;/h2&gt;

&lt;p&gt;If Tailwind 4 is not already configured, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx astro add tailwind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review and accept the proposed changes. The command adds &lt;code&gt;@tailwindcss/vite&lt;/code&gt; to &lt;code&gt;astro.config.mjs&lt;/code&gt; and creates &lt;code&gt;src/styles/global.css&lt;/code&gt; with the Tailwind import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'tailwindcss'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import that stylesheet in the front matter of a shared layout, such as &lt;code&gt;src/layouts/Layout.astro&lt;/code&gt;, or in each page that needs it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import '../styles/global.css'
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Merge the import into your existing front matter; keep the rest of the layout. The CSS must be imported by a layout or page you actually render. See &lt;a href="https://docs.astro.build/en/guides/styling/#add-tailwind-4" rel="noopener noreferrer"&gt;Astro's Tailwind setup instructions&lt;/a&gt; if your project uses a different structure. Existing Tailwind 3 projects should follow the migration instructions there first.&lt;/p&gt;

&lt;p&gt;Then install Prettier and both formatting plugins:&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; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; prettier@3 prettier-plugin-astro prettier-plugin-tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--save-exact&lt;/code&gt; saves exact versions in &lt;code&gt;package.json&lt;/code&gt;. Commit &lt;code&gt;package-lock.json&lt;/code&gt; as well so teammates and CI can install the same dependency tree with &lt;code&gt;npm ci&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Prettier
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.prettierrc&lt;/code&gt; in the project root, or merge these settings into your existing Prettier configuration:&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;"useTabs"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"singleQuote"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trailingComma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"semi"&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="nl"&gt;"printWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&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="s2"&gt;"prettier-plugin-astro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier-plugin-tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tailwindStylesheet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/styles/global.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&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="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.astro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"parser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro"&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;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;The first five options are formatting preferences, not Astro requirements. Adjust them to your team's conventions.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;plugins&lt;/code&gt; array explicitly loads the plugins. &lt;a href="https://prettier.io/blog/2023/07/05/3.0.0#plugin-search-feature-has-been-removed-14759-by-fisker" rel="noopener noreferrer"&gt;Prettier 3 removed automatic plugin discovery&lt;/a&gt;; installing a package alone does not load it in the CLI. The Astro parser override follows the &lt;a href="https://github.com/withastro/prettier-plugin-astro#recommended-configuration" rel="noopener noreferrer"&gt;Astro plugin's recommended configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For Tailwind 4, &lt;code&gt;tailwindStylesheet&lt;/code&gt; is required by the &lt;a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss#specifying-your-tailwind-stylesheet-path-tailwind-css-v4" rel="noopener noreferrer"&gt;Tailwind plugin's setup instructions&lt;/a&gt;. Point it to your actual CSS entry point containing the Tailwind import and your theme or custom utility definitions. The path is relative to &lt;code&gt;.prettierrc&lt;/code&gt;, not the file being formatted.&lt;/p&gt;

&lt;p&gt;Keep &lt;code&gt;prettier-plugin-tailwindcss&lt;/code&gt; last in the plugin array. If your project also uses Svelte, install its formatter:&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; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; prettier-plugin-svelte
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then replace the &lt;code&gt;plugins&lt;/code&gt; array with this one, keeping the other settings:&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="s2"&gt;"prettier-plugin-astro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier-plugin-svelte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier-plugin-tailwindcss"&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;Add &lt;code&gt;.prettierignore&lt;/code&gt; for generated output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/
.astro/
.vercel/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prettier already ignores &lt;code&gt;node_modules&lt;/code&gt; by default. There is no general reason to exclude &lt;code&gt;vercel.json&lt;/code&gt;; it is a source configuration file that Prettier can format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting on save in VS Code
&lt;/h2&gt;

&lt;p&gt;Merge these settings into &lt;code&gt;.vscode/settings.json&lt;/code&gt;:&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;"editor.formatOnSave"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esbenp.prettier-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prettier.documentSelectors"&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="s2"&gt;"**/*.astro"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"[astro]"&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;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esbenp.prettier-vscode"&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;The Astro extension has its own formatter; these settings explicitly select the Prettier extension for &lt;code&gt;.astro&lt;/code&gt; files. The Prettier extension uses your project's locally installed Prettier. Open a trusted workspace and approve local module loading if prompted.&lt;/p&gt;

&lt;p&gt;For Svelte projects, also add &lt;code&gt;"**/*.svelte"&lt;/code&gt; to &lt;code&gt;prettier.documentSelectors&lt;/code&gt; and a &lt;code&gt;[svelte]&lt;/code&gt; setting with the same &lt;code&gt;editor.defaultFormatter&lt;/code&gt; value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the setup locally and in CI
&lt;/h2&gt;

&lt;p&gt;Format the project, then check it without making changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prettier &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--write&lt;/span&gt;
npx prettier &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the formatting diff before committing. &lt;code&gt;--check&lt;/code&gt; exits with a nonzero status if files need formatting, so it can fail a CI job. In CI, install the committed dependencies with &lt;code&gt;npm ci&lt;/code&gt; before running &lt;code&gt;npx prettier . --check&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If formatting fails, check that the stylesheet path exists, the Astro parser override is present, and the Tailwind plugin is last. If the CLI works but saving in VS Code does not, check the selected formatter and the Prettier output panel.&lt;/p&gt;

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

&lt;p&gt;The project now has an explicit plugin configuration, a Tailwind 4 stylesheet reference, and a formatting check you can use outside the editor. Keep the configuration and lockfile in Git so format-on-save and CI use the same rules.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://straffesites.com/en/blog/astro-setup-svelte-tailwindcss-vercel-prettier-typescript" rel="noopener noreferrer"&gt;full Astro setup guide&lt;/a&gt; covers the wider starter configuration, including Svelte, Vercel, and TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://straffesites.com/en/blog/integrate-prettier-astro-tailwindcss" rel="noopener noreferrer"&gt;straffesites.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>prettier</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>A guide to proper semantic markup for blog posts</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Thu, 03 Sep 2026 11:06:26 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/a-guide-to-proper-semantic-markup-for-blog-posts-lna</link>
      <guid>https://dev.to/seppegadeyne/a-guide-to-proper-semantic-markup-for-blog-posts-lna</guid>
      <description>&lt;p&gt;Semantic markup means choosing HTML elements for their meaning: one &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; for your title, &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; for logical sections, &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; with &lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt; for images, &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; for your publication date, and JSON-LD BlogPosting data for search engines. This guide explains why that matters for SEO and gives you examples of each element that you can copy, paste, and validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to semantic markup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Semantics#semantics_in_html" rel="noopener noreferrer"&gt;Semantic markup&lt;/a&gt; uses HTML elements to make the structure and meaning of a page clear to browsers, search engines, and assistive technology. Give the blog post one &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; for its title. Below it, use a logical hierarchy without skipping levels: &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; for main sections and &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; for subsections. This makes the article easier to scan and understand.&lt;/p&gt;

&lt;p&gt;You can also add an &lt;code&gt;id&lt;/code&gt; attribute to each &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt;, usually based on a short hyphenated version of the heading. A table of contents can then link directly to it with a fragment such as &lt;code&gt;#name-of-the-id&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic structure of a blog post
&lt;/h2&gt;

&lt;p&gt;Here is an example of the basic structure of a blog post with semantic markup:&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My website&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"logo.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Company name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Main navigation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- aria-label instead of a heading: navigation doesn't belong in the content outline --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;menu&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/contact"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Contact&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/menu&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Main title&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Title 1&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph 1&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Title 2&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph 2&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Title 3&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph 3&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Footer navigation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;menu&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/contact"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Contact&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/menu&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;copy;&lt;/span&gt; 2023 Company name&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the navigation does not need an &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt;. Headings describe content sections; an &lt;code&gt;aria-label&lt;/code&gt; names the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; landmark instead. The heading hierarchy stays clean, while screen reader users can still distinguish one navigation area from another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Images and their alt text
&lt;/h2&gt;

&lt;p&gt;Images need the same care as the surrounding text. Use descriptive filenames and write alt text that explains the content or purpose of each informative image. Alt text is primarily an accessibility feature, though it can also give search engines useful context.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt; provides a visible caption that connects the image to your article. The alt text describes the image for someone who cannot see it; the caption is visible to everyone. The two may overlap, but they do not need to repeat each other word for word.&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;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"example-image.jpg"&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"An example of a well-formatted image"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;An example of a well-formatted image&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;address&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; for a machine-readable publication or modification date. The &lt;code&gt;&amp;lt;address&amp;gt;&lt;/code&gt; element is suitable for the article author's contact information, not for every postal address that happens to appear in the copy. Link to a useful author page if you have one.&lt;/p&gt;

&lt;p&gt;Here is a simple example using &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;address&amp;gt;&lt;/code&gt;:&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;time&lt;/span&gt; &lt;span class="na"&gt;datetime=&lt;/span&gt;&lt;span class="s"&gt;"2023-03-21"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;March 21, 2023&lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;address&amp;gt;&lt;/span&gt;
  Written by &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"author-page.html"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;The Author&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  Follow me on &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://twitter.com/theauthor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Twitter&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/address&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Schema markup for blog posts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data" rel="noopener noreferrer"&gt;Schema markup&lt;/a&gt; gives search engines an explicit description of the page. A blog post can use &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/article" rel="noopener noreferrer"&gt;Article&lt;/a&gt; or its more specific &lt;code&gt;BlogPosting&lt;/code&gt; type. If the page contains a genuine FAQ, &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/faqpage" rel="noopener noreferrer"&gt;FAQPage&lt;/a&gt; can describe those questions and answers too.&lt;/p&gt;

&lt;p&gt;Here is a &lt;code&gt;BlogPosting&lt;/code&gt; example:&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;"@context"&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://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BlogPosting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A guide to the right semantic markup for blog posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&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://example.com/blog/semantic-markup-blog-posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image"&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;"https://example.com/image/1x1/image.jpg"&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://example.com/image/4x3/image.jpg"&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://example.com/image/16x9/image.jpg"&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;"datePublished"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-03-20T12:42:21.345Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dateModified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-03-21T12:12:44.220Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Author"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&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://example.com/the-author"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&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;"https://twitter.com/the-author"&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://www.linkedin.com/in/the-author/"&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="nl"&gt;"publisher"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Company name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&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://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&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="s2"&gt;"https://www.linkedin.com/company/company-name/"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logo"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ImageObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&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://example.com/logo.svg"&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;And here is an &lt;code&gt;FAQPage&lt;/code&gt; example using &lt;code&gt;Question&lt;/code&gt; and &lt;code&gt;Answer&lt;/code&gt;:&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;"@context"&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://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAQPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mainEntity"&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="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Why is semantic markup important for SEO?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Semantic markup helps search engines understand the structure and meaning of a page and gives assistive technologies useful relationships. It can support indexing, but it does not guarantee rankings or traffic."&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;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Should I always use alt text for images?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Use descriptive alt text for informative images. Give purely decorative images an empty alt attribute so screen readers can skip them. Alt text primarily supports accessibility and can also provide search engines with useful context."&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;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;Always validate pasted markup. Google's &lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;Rich Results Test&lt;/a&gt; shows whether Google recognizes supported structured data, while the &lt;a href="https://validator.schema.org/" rel="noopener noreferrer"&gt;Schema.org validator&lt;/a&gt; checks the underlying vocabulary. URL inspection in Google Search Console shows how Google sees the published page. As a quick local check, the JSON must be accepted by &lt;code&gt;JSON.parse&lt;/code&gt; without HTML-entity corruption.&lt;/p&gt;

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

&lt;p&gt;Correct semantic markup gives readers, assistive technology, and search engines a clearer article. Use headings in order, describe informative images with useful alt text, and add valid schema markup where it accurately reflects the page. For the editorial side, I also wrote a guide on &lt;a href="https://straffesites.com/en/blog/blog-post-structure-tips" rel="noopener noreferrer"&gt;formatting and structuring a blog post&lt;/a&gt;. MDN has a useful reference on &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Semantics#semantics_in_html" rel="noopener noreferrer"&gt;semantics in HTML&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was originally published on &lt;a href="https://straffesites.com/en/blog/a-guide-to-semantics-for-blog-posts" rel="noopener noreferrer"&gt;straffesites.com&lt;/a&gt; — I build fast, semantic websites there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>seo</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Supercharge your Shopify pages with Liquid shortcodes for collections</title>
      <dc:creator>Seppe Gadeyne</dc:creator>
      <pubDate>Fri, 02 Jun 2023 11:33:14 +0000</pubDate>
      <link>https://dev.to/seppegadeyne/supercharge-your-shopify-pages-with-liquid-shortcodes-for-collections-32l3</link>
      <guid>https://dev.to/seppegadeyne/supercharge-your-shopify-pages-with-liquid-shortcodes-for-collections-32l3</guid>
      <description>&lt;p&gt;I will share a handy trick I recently applied to a client's website selling &lt;a href="https://rugexpertiseshop.nl/pages/steunzolen-bekkenpijn" rel="noopener noreferrer"&gt;orthopedic insoles&lt;/a&gt;. We needed a way to showcase collections in a page's text. Unlike WordPress, this isn't a feature directly offered by Shopify. We can achieve this with some Liquid, Shopify's template language. Look at the link above for an example of this implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your Shopify theme shortcode-ready
&lt;/h2&gt;

&lt;p&gt;The first step in our journey involves tweaking your theme's &lt;code&gt;main-page.liquid&lt;/code&gt; file, located under the &lt;code&gt;/sections&lt;/code&gt; directory. We'll make a small but impactful change here: replace &lt;code&gt;{{ page.content }}&lt;/code&gt; with &lt;code&gt;{% render 'shortcode', page: page.content %}&lt;/code&gt;. This modification tells Shopify to load the snippet named &lt;code&gt;shortcode.liquid&lt;/code&gt; and pass it the variable named &lt;code&gt;page&lt;/code&gt; containing &lt;code&gt;page.content&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the liquid shortcodes
&lt;/h2&gt;

&lt;p&gt;Once we've prepped our theme, it's time to create two new files within the &lt;code&gt;/snippets&lt;/code&gt; folder: &lt;code&gt;shortcode.liquid&lt;/code&gt; and &lt;code&gt;shortcode-collection.liquid&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;shortcode.liquid&lt;/code&gt;, we'll be inserting the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;liquid&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;page&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'[collection='&lt;/span&gt;&lt;span class="w"&gt;

       &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content_parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;']'&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;shortcode_handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content_parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

           &lt;/span&gt;&lt;span class="nt"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

           &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;shortcode_handle&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nt"&gt;render&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'shortcode-collection'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;shortcode_handle&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nt"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nt"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;p&amp;gt;No collection found with the handle "'&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;shortcode_handle&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'".&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt;

           &lt;/span&gt;&lt;span class="nt"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content_parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nt"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nt"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;page&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code checks if a shortcode in the format &lt;code&gt;[collection=name-collection]&lt;/code&gt; is present within the text. If detected, the content will be split into two parts, and &lt;code&gt;shortcode-collection.liquid&lt;/code&gt; will be loaded. If no shortcode is found, &lt;code&gt;page.content&lt;/code&gt; is loaded as is. &lt;/p&gt;

&lt;p&gt;Now, we'll add the following code to &lt;code&gt;shortcode-collection.liquid&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
   &amp;lt;article&amp;gt;
       &amp;lt;h2&amp;gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&amp;lt;/h2&amp;gt;
       &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
           &amp;lt;article&amp;gt;
               &amp;lt;a href='&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;'&amp;gt;
                   &amp;lt;img
                       src='&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;featured_image&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="nf"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'600'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'600'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;'
                       alt='&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;'
                       width='600'
                       height='600'
                       loading='lazy'
                   &amp;gt;
               &amp;lt;/a&amp;gt;

               &amp;lt;a href='&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;'&amp;gt;
                   &amp;lt;h3&amp;gt;
                       &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
                   &amp;lt;/h3&amp;gt;
               &amp;lt;/a&amp;gt;

               &amp;lt;p&amp;gt;
                   &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;price&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="nf"&gt;money&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
               &amp;lt;/p&amp;gt;
           &amp;lt;/article&amp;gt;
       &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endfor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
   &amp;lt;/article&amp;gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
   &amp;lt;p&amp;gt;No collection found with the handle "&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;shortcode_handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;".&amp;lt;/p&amp;gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is responsible for rendering the products of a given collection. Remember, you'll likely need to style the output with CSS to match your website's looks. I intentionally used the &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; element, along with heading elements &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt;, to leverage semantic markup, which can help your site rank higher on Google. For more information on &lt;a href="https://dev.to/seppegadeyne/unlocking-the-power-of-semantics-in-html-4e9j"&gt;semantic markup&lt;/a&gt; and improving your Google ranking, check out my other articles at &lt;a href="https://straffesites.com/en/blog" rel="noopener noreferrer"&gt;Straffe Sites&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;With some creativity and Liquid code, we've created a system that allows for the flexible insertion of Shopify collections into any part of your webpage.&lt;/p&gt;

&lt;p&gt;This technique can be a real game-changer for those who want to provide a more dynamic and customized shopping experience. It's another example of the flexibility and power that Shopify's Liquid language offers.&lt;/p&gt;

&lt;p&gt;But remember, with great power comes great responsibility. Always double-check your code and test your pages thoroughly to ensure everything works as expected. And remember to style your output with CSS to maintain a consistent and appealing visual aesthetic across your site.&lt;/p&gt;

&lt;p&gt;I hope this tutorial has been helpful and has given you ideas for your Shopify projects. Remember, the possibilities are nearly endless when customizing your e-commerce store. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>shopify</category>
      <category>liquid</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
