{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Blog — Mojtaba Norouzi",
  "home_page_url": "https://mojtaba.tech/blog/",
  "feed_url": "https://mojtaba.tech/blog/feed.json",
  "description": "Notes, essays and build logs on backend engineering, distributed systems, AI — and the occasional detour into Persian music.",
  "language": "en",
  "authors": [
    {
      "name": "Mojtaba Norouzi",
      "url": "https://mojtaba.tech"
    }
  ],
  "items": [
    {
      "id": "https://mojtaba.tech/blog/how-this-blog-is-built/",
      "url": "https://mojtaba.tech/blog/how-this-blog-is-built/",
      "title": "How this blog is built",
      "summary": "A Markdown folder, one Node script, and no framework. The build log for the publishing system you are currently reading.",
      "content_html": "<p>The rest of this site is three files: <code>index.html</code>, <code>styles.css</code>, <code>script.js</code>. No build\nstep, no framework, no <code>node_modules</code> in sight. I liked it that way, and I did not want\na blog to be the thing that dragged in a bundler.</p>\n<p>So the constraint I set myself was: <strong>adding a post must not make the site heavier for\nthe reader, and must not make the repository harder to understand.</strong></p>\n<h2 id=\"what-it-does\">What it does<a class=\"heading-anchor\" href=\"#what-it-does\" aria-label=\"Permalink: What it does\"><span aria-hidden=\"true\">#</span></a></h2>\n<p>Every post is a Markdown file in <code>content/blog/</code>. A Node script reads them at build\ntime and writes plain HTML into <code>blog/</code>. The browser receives no Markdown, no parser,\nand no JavaScript that is not already on the homepage.</p>\n<div class=\"code-block\" data-lang=\"text\"><pre dir=\"ltr\" tabindex=\"0\" role=\"region\" aria-label=\"Code sample, text\"><code class=\"language-text\">content/blog/my-post.md   →   blog/my-post/index.html\n                          →   blog/index.html\n                          →   blog/tags/&lt;tag&gt;/index.html\n                          →   blog/rss.xml, blog/feed.json\n                          →   sitemap.xml, robots.txt</code></pre></div>\n<p>The generated directory is gitignored. It is rebuilt in CI on every push, immediately\nbefore the GitHub Pages artifact is uploaded — which is exactly how the résumé PDF on\nthe homepage already worked. I did not invent a pipeline; I added a step to one that\nexisted.</p>\n<h2 id=\"frontmatter\">Frontmatter<a class=\"heading-anchor\" href=\"#frontmatter\" aria-label=\"Permalink: Frontmatter\"><span aria-hidden=\"true\">#</span></a></h2>\n<p>Each file opens with a small metadata block:</p>\n<div class=\"code-block\" data-lang=\"yaml\"><pre dir=\"ltr\" tabindex=\"0\" role=\"region\" aria-label=\"Code sample, yaml\"><code class=\"language-yaml\">---\ntitle: Ordering is a local property\ndescription: Message ordering is not something a broker hands you globally.\ndate: 2026-08-05\nupdated: 2026-08-09\ntype: article\ntags: [Kafka, distributed-systems]\nfeatured: true\ndraft: false\n---</code></pre></div>\n<p><code>type</code> is the interesting field. I write four different kinds of thing — long articles,\nshort notes, reactions to something I read, and build logs like this one — and my first\ninstinct was four separate systems. That was wrong. They differ in <em>presentation</em>, not\nin structure: same title, same date, same body. One content model with a <code>type</code> field\ndoes the whole job, and the templates decide that a note does not need a reading-time\nbadge.</p>\n<h2 id=\"the-parts-worth-mentioning\">The parts worth mentioning<a class=\"heading-anchor\" href=\"#the-parts-worth-mentioning\" aria-label=\"Permalink: The parts worth mentioning\"><span aria-hidden=\"true\">#</span></a></h2>\n<p><strong>One dependency.</strong> <code>marked</code>, for Markdown. It has no transitive dependencies and it\nruns only at build time. I parse the frontmatter myself — it is eight known keys, and\npulling a full YAML engine in to read eight known keys is not a trade I wanted.</p>\n<p><strong>Headings become anchors.</strong> Every <code>##</code> gets a stable, Unicode-safe id and a permalink,\nso you can link someone to the paragraph that matters instead of to the top of a\ntwo-thousand-word page.</p>\n<p><strong>Direction is per block, not per page.</strong> I write in English and in Persian, sometimes\nin the same paragraph. The build tags each block with the direction that most of its\ncharacters actually want, rather than trusting the first-strong-character rule that\n<code>dir=&quot;auto&quot;</code> uses — which gets a Persian sentence wrong the moment it opens with an\nEnglish technical term.</p>\n<p><strong>Drafts are not built.</strong> A post with <code>draft: true</code> produces no file at all. There is no\npage to leak, no URL to guess, and nothing in the sitemap or feeds. Locally I run:</p>\n<div class=\"code-block\" data-lang=\"bash\"><pre dir=\"ltr\" tabindex=\"0\" role=\"region\" aria-label=\"Code sample, bash\"><code class=\"language-bash\">BLOG_DRAFTS=1 node tools/blog/build.mjs</code></pre></div>\n<p>which is the only way a draft ever becomes HTML.</p>\n<h2 id=\"what-i-deliberately-left-out\">What I deliberately left out<a class=\"heading-anchor\" href=\"#what-i-deliberately-left-out\" aria-label=\"Permalink: What I deliberately left out\"><span aria-hidden=\"true\">#</span></a></h2>\n<p>Syntax highlighting, for now. Every option costs either a large build-time grammar\nbundle or client-side JavaScript, and monospaced code with real contrast and generous\nline height reads fine. If I start posting more code than prose I will revisit it — the\nhook is a single function in <code>tools/blog/markdown.mjs</code>.</p>\n<p>Comments, analytics, a newsletter, and search. There are a handful of posts here. A\nsearch box would be decoration.</p>\n<p>Pagination. At some point a chronological index gets silly, but &quot;some point&quot; is a long\nway from here, and building for it now would mean guessing at a problem I do not have.</p>\n<h2 id=\"the-whole-publishing-workflow\">The whole publishing workflow<a class=\"heading-anchor\" href=\"#the-whole-publishing-workflow\" aria-label=\"Permalink: The whole publishing workflow\"><span aria-hidden=\"true\">#</span></a></h2>\n<div class=\"code-block\" data-lang=\"bash\"><pre dir=\"ltr\" tabindex=\"0\" role=\"region\" aria-label=\"Code sample, bash\"><code class=\"language-bash\"># 1. write\n$EDITOR content/blog/something-i-learned.md\n\n# 2. preview\nnode tools/blog/build.mjs &amp;&amp; python -m http.server 8123\n\n# 3. publish\ngit add content/blog/something-i-learned.md &amp;&amp; git commit &amp;&amp; git push</code></pre></div>\n<p>Step three triggers CI, which rebuilds the site and the PDF and deploys. There is no\nstep four, and there is no admin panel to log into — which was the entire objective.</p>\n",
      "date_published": "2026-08-05T00:00:00.000Z",
      "tags": [
        "meta",
        "static-sites",
        "tooling"
      ],
      "language": "en",
      "_blog": {
        "type": "build",
        "type_label": "Build log",
        "path": "/blog/how-this-blog-is-built/",
        "reading_minutes": 3
      }
    },
    {
      "id": "https://mojtaba.tech/blog/fa-idempotency/",
      "url": "https://mojtaba.tech/blog/fa-idempotency/",
      "title": "یادداشتی دربارهٔ idempotency",
      "summary": "چرا در سیستم‌های پیام‌محور، «دوباره اجرا شدن» یک استثنا نیست و باید از ابتدا برایش طراحی کنیم.",
      "content_html": "<p>هر سیستم پیام‌محوری که با تضمین at-least-once کار می‌کند، دیر یا زود یک پیام را دوبار\nتحویل می‌دهد. این باگ نیست؛ بخشی از قرارداد است. اگر broker مطمئن نباشد که پیام را\nپردازش کرده‌اید، دوباره می‌فرستد — و این دقیقاً همان رفتاری است که از او خواسته‌ایم.</p>\n<p>مشکل وقتی جدی می‌شود که <code>handler</code> ما فرض کرده باشد هر پیام دقیقاً یک بار می‌آید. آن وقت\nیک <code>retry</code> ساده می‌تواند موجودی انبار را دوبار کم کند یا برای یک سفارش دو بار فاکتور\nبزند.</p>\n<p>راه‌حل معمولاً پیچیده نیست. کافی است هر پیام شناسهٔ یکتا داشته باشد و پیش از اعمال،\nبررسی کنیم که قبلاً آن را دیده‌ایم یا نه:</p>\n<div class=\"code-block\" data-lang=\"csharp\"><pre dir=\"ltr\" tabindex=\"0\" role=\"region\" aria-label=\"Code sample, csharp\"><code class=\"language-csharp\">if (await _processed.ExistsAsync(message.Id))\n{\n    return HandlerResult.Skip;\n}\n\nawait using var tx = await _db.BeginTransactionAsync();\nawait _inventory.DecreaseAsync(message.Sku, message.Quantity);\nawait _processed.AddAsync(message.Id);\nawait tx.CommitAsync();</code></pre></div>\n<p>نکتهٔ ظریف این است که ثبت شناسه و خودِ تغییر وضعیت باید در یک تراکنش انجام شوند. اگر\nآن‌ها را جدا کنیم، باز هم به همان مشکل برمی‌گردیم — فقط با پنجرهٔ زمانی کوچک‌تری که\nپیدا کردنش سخت‌تر است.</p>\n<p dir=\"ltr\">The short version in English: at-least-once delivery makes idempotency a requirement,\nnot an optimisation. Design for the duplicate on day one and you never have to hunt it\ndown at two in the morning.</p>\n",
      "date_published": "2026-07-25T00:00:00.000Z",
      "tags": [
        "Kafka",
        "event-driven",
        "Persian"
      ],
      "language": "fa",
      "_blog": {
        "type": "note",
        "type_label": "Note",
        "path": "/blog/fa-idempotency/",
        "reading_minutes": 1
      }
    }
  ]
}
