{"page":1,"limit":20,"total":95,"totalPages":5,"todayTotal":4,"data":[{"source":"https://dev.to/feed/tag/typescript","sourceHost":"dev.to","title":"Private Media Text-to-JSON Extraction: Long-Document Token Limits and Timeout Control","link":"https://dev.to/humphreyfox1243/private-media-text-to-json-extraction-long-document-token-limits-and-timeout-control-4cge","pubDate":"Tue, 22 Sep 2026 13:37:29 +0000","description":"<p>Fix long-document extraction timeouts by reducing the evidence before asking a model for JSON. Count tokens, split the source, use embeddings and reranking to select passages for the fields you need, then merge small validated results in application code. For private media archives, this keeps structured-output correctness visible instead of hiding it inside one long request.</p>\n\n<p><strong>TL;DR:</strong> treat extraction as a batch pipeline with observable stages, not a single chat call. A larger context window can delay the timeout, but it does not define conflict policy, improve evidence selection, or make an import job fit an interactive HTTP deadline.</p>\n\n<p>There is an operational choice around that pipeline, too. One key and one bill can replace credentials and invoices scattered across the token-counting, reranking, and extraction services. A single REST API also means no SDK to install: any language or runtime can send the same plain HTTP requests, which keeps a later batch-worker rewrite from forcing a new service integration.</p>\n\n<h2>\n  \n  \n  Replace one opaque request with observable stages\n</h2>\n\n<p>The fragile mental model is short: document in, model call, JSON out. One request must locate evidence, follow a schema, resolve contradictions, and finish before a timeout. When it fails, the only useful signal may be elapsed time.</p>\n\n<p>The better model is a conveyor belt described in words: token count -&gt; bounded chunks -&gt; embeddings -&gt; field-specific retrieval -&gt; rerank -&gt; schema-bound extraction -&gt; deterministic merge -&gt; validation. Each arrow is a place to record progress and reject bad state.</p>\n\n<p>This matters in a media knowledge base. A long interview can identify a guest near the beginning, correct the spelling of that name much later, and put publication restrictions in the closing notes. Selecting only the opening loses evidence. Sending the full transcript makes evidence search and JSON generation compete inside the same deadline.</p>\n\n<p>Batch processing is the safer default for archive imports and other long jobs. Interactive request-response extraction still fits short items and previews. The trade-off is explicit: batch work adds job state and delayed completion, but an HTTP timeout no longer determines whether the extraction can finish.</p>\n\n<p>Keep the stages boring. That is useful.</p>\n\n<h2>\n  \n  \n  How should a long text-to-JSON timeout be fixed?\n</h2>\n\n<p>Start by changing the unit of work. The useful unit is a field-specific evidence set, not the whole document. A query for <code>people</code> needs different passages from a query for <code>publicationRestrictions</code>, so retrieval should follow the target schema rather than reuse one vague document summary.</p>\n\n<p>This TypeScript example is runnable orchestration code. It keeps provider-specific calls behind typed adapters, which makes chunk selection, retry behavior, and merging testable without putting private source text in logs. The <code>2_000</code>-token budget and top four results are starting controls, not measured universal limits.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">import</span> <span class=\"nx\">OpenAI</span> <span class=\"k\">from</span> <span class=\"dl\">\"</span><span class=\"s2\">openai</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n\n<span class=\"kd\">type</span> <span class=\"nx\">MediaRecord</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"na\">people</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[];</span>\n  <span class=\"nl\">organizations</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[];</span>\n  <span class=\"nl\">topics</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[];</span>\n  <span class=\"nl\">publicationRestrictions</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[];</span>\n<span class=\"p\">};</span>\n\n<span class=\"kd\">type</span> <span class=\"nx\">RankedChunk</span> <span class=\"o\">=</span> <span class=\"p\">{</span> <span class=\"na\">index</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">;</span> <span class=\"nl\">score</span><span class=\"p\">:</span> <span class=\"kr\">number</span> <span class=\"p\">};</span>\n<span class=\"kd\">type</span> <span class=\"nx\">CountTokens</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">text</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"kr\">number</span><span class=\"o\">&gt;</span><span class=\"p\">;</span>\n<span class=\"kd\">type</span> <span class=\"nx\">Embed</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">chunks</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[])</span> <span class=\"o\">=&gt;</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"kr\">number</span><span class=\"p\">[][]</span><span class=\"o\">&gt;</span><span class=\"p\">;</span>\n<span class=\"kd\">type</span> <span class=\"nx\">Rerank</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">query</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">,</span> <span class=\"nx\">chunks</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[])</span> <span class=\"o\">=&gt;</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nx\">RankedChunk</span><span class=\"p\">[]</span><span class=\"o\">&gt;</span><span class=\"p\">;</span>\n<span class=\"kd\">type</span> <span class=\"nb\">Extract</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">evidence</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nx\">MediaRecord</span><span class=\"o\">&gt;</span><span class=\"p\">;</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">apiKey</span> <span class=\"o\">=</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">INFRAI_API_KEY</span><span class=\"p\">;</span>\n<span class=\"kd\">const</span> <span class=\"nx\">model</span> <span class=\"o\">=</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">INFRAI_MODEL</span><span class=\"p\">;</span>\n<span class=\"kd\">const</span> <span class=\"nx\">baseURL</span> <span class=\"o\">=</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">INFRAI_BASE_URL</span><span class=\"p\">;</span>\n<span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">apiKey</span> <span class=\"o\">||</span> <span class=\"o\">!</span><span class=\"nx\">model</span> <span class=\"o\">||</span> <span class=\"o\">!</span><span class=\"nx\">baseURL</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n  <span class=\"k\">throw</span> <span class=\"k\">new</span> <span class=\"nc\">Error</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">Set INFRAI_API_KEY, INFRAI_MODEL, and INFRAI_BASE_URL</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">client</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">OpenAI</span><span class=\"p\">({</span>\n  <span class=\"nx\">apiKey</span><span class=\"p\">,</span>\n  <span class=\"nx\">baseURL</span><span class=\"p\">,</span>\n  <span class=\"na\">maxRetries</span><span class=\"p\">:</span> <span class=\"mi\">0</span><span class=\"p\">,</span>\n<span class=\"p\">});</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">wait</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">milliseconds</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span>\n  <span class=\"k\">new</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"k\">void</span><span class=\"o\">&gt;</span><span class=\"p\">((</span><span class=\"nx\">resolve</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nf\">setTimeout</span><span class=\"p\">(</span><span class=\"nx\">resolve</span><span class=\"p\">,</span> <span class=\"nx\">milliseconds</span><span class=\"p\">));</span>\n\n<span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">withRateLimitRetry</span><span class=\"o\">&lt;</span><span class=\"nx\">T</span><span class=\"o\">&gt;</span><span class=\"p\">(</span><span class=\"nx\">operation</span><span class=\"p\">:</span> <span class=\"p\">()</span> <span class=\"o\">=&gt;</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nx\">T</span><span class=\"o\">&gt;</span><span class=\"p\">):</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nx\">T</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n  <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">let</span> <span class=\"nx\">attempt</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span> <span class=\"nx\">attempt</span> <span class=\"o\">&lt;</span> <span class=\"mi\">4</span><span class=\"p\">;</span> <span class=\"nx\">attempt</span> <span class=\"o\">+=</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"k\">try</span> <span class=\"p\">{</span>\n      <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"nf\">operation</span><span class=\"p\">();</span>\n    <span class=\"p\">}</span> <span class=\"k\">catch </span><span class=\"p\">(</span><span class=\"nx\">error</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">status</span> <span class=\"o\">=</span>\n        <span class=\"k\">typeof</span> <span class=\"nx\">error</span> <span class=\"o\">===</span> <span class=\"dl\">\"</span><span class=\"s2\">object</span><span class=\"dl\">\"</span> <span class=\"o\">&amp;&amp;</span> <span class=\"nx\">error</span> <span class=\"o\">!==</span> <span class=\"kc\">null</span> <span class=\"o\">&amp;&amp;</span> <span class=\"dl\">\"</span><span class=\"s2\">status</span><span class=\"dl\">\"</span> <span class=\"k\">in</span> <span class=\"nx\">error</span>\n          <span class=\"p\">?</span> <span class=\"nc\">Number</span><span class=\"p\">(</span><span class=\"nx\">error</span><span class=\"p\">.</span><span class=\"nx\">status</span><span class=\"p\">)</span>\n          <span class=\"p\">:</span> <span class=\"kc\">undefined</span><span class=\"p\">;</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">retryAfter</span> <span class=\"o\">=</span> <span class=\"nx\">error</span> <span class=\"k\">instanceof</span> <span class=\"nx\">OpenAI</span><span class=\"p\">.</span><span class=\"nx\">APIError</span>\n        <span class=\"p\">?</span> <span class=\"nc\">Number</span><span class=\"p\">(</span><span class=\"nx\">error</span><span class=\"p\">.</span><span class=\"nx\">headers</span><span class=\"p\">?.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">retry-after</span><span class=\"dl\">\"</span><span class=\"p\">))</span>\n        <span class=\"p\">:</span> <span class=\"nb\">Number</span><span class=\"p\">.</span><span class=\"kc\">NaN</span><span class=\"p\">;</span>\n\n      <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">status</span> <span class=\"o\">!==</span> <span class=\"mi\">429</span> <span class=\"o\">||</span> <span class=\"nx\">attempt</span> <span class=\"o\">===</span> <span class=\"mi\">3</span><span class=\"p\">)</span> <span class=\"k\">throw</span> <span class=\"nx\">error</span><span class=\"p\">;</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">delay</span> <span class=\"o\">=</span> <span class=\"nb\">Number</span><span class=\"p\">.</span><span class=\"nf\">isFinite</span><span class=\"p\">(</span><span class=\"nx\">retryAfter</span><span class=\"p\">)</span>\n        <span class=\"p\">?</span> <span class=\"nx\">retryAfter</span> <span class=\"o\">*</span> <span class=\"mi\">1</span><span class=\"nx\">_000</span>\n        <span class=\"p\">:</span> <span class=\"mi\">2</span> <span class=\"o\">**</span> <span class=\"nx\">attempt</span> <span class=\"o\">*</span> <span class=\"mi\">1</span><span class=\"nx\">_000</span><span class=\"p\">;</span>\n      <span class=\"k\">await</span> <span class=\"nf\">wait</span><span class=\"p\">(</span><span class=\"nx\">delay</span><span class=\"p\">);</span>\n    <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n  <span class=\"k\">throw</span> <span class=\"k\">new</span> <span class=\"nc\">Error</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">Retry budget exhausted</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">extract</span><span class=\"p\">:</span> <span class=\"nb\">Extract</span> <span class=\"o\">=</span> <span class=\"k\">async </span><span class=\"p\">(</span><span class=\"nx\">evidence</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">client</span><span class=\"p\">.</span><span class=\"nx\">chat</span><span class=\"p\">.</span><span class=\"nx\">completions</span><span class=\"p\">.</span><span class=\"nf\">create</span><span class=\"p\">({</span>\n    <span class=\"nx\">model</span><span class=\"p\">,</span>\n    <span class=\"na\">messages</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n      <span class=\"p\">{</span>\n        <span class=\"na\">role</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">system</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n        <span class=\"na\">content</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">Extract media facts from evidence. Do not infer missing facts.</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n      <span class=\"p\">},</span>\n      <span class=\"p\">{</span> <span class=\"na\">role</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">user</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">content</span><span class=\"p\">:</span> <span class=\"nx\">evidence</span> <span class=\"p\">},</span>\n    <span class=\"p\">],</span>\n    <span class=\"na\">response_format</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n      <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">json_schema</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n      <span class=\"na\">json_schema</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n        <span class=\"na\">name</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">media_record</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n        <span class=\"na\">strict</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n        <span class=\"na\">schema</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n          <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">object</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n          <span class=\"na\">additionalProperties</span><span class=\"p\">:</span> <span class=\"kc\">false</span><span class=\"p\">,</span>\n          <span class=\"na\">properties</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n            <span class=\"na\">people</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">array</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">items</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">string</span><span class=\"dl\">\"</span> <span class=\"p\">}</span> <span class=\"p\">},</span>\n            <span class=\"na\">organizations</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">array</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">items</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">string</span><span class=\"dl\">\"</span> <span class=\"p\">}</span> <span class=\"p\">},</span>\n            <span class=\"na\">topics</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">array</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">items</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">string</span><span class=\"dl\">\"</span> <span class=\"p\">}</span> <span class=\"p\">},</span>\n            <span class=\"na\">publicationRestrictions</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n              <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">array</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n              <span class=\"na\">items</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">string</span><span class=\"dl\">\"</span> <span class=\"p\">},</span>\n            <span class=\"p\">},</span>\n          <span class=\"p\">},</span>\n          <span class=\"na\">required</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n            <span class=\"dl\">\"</span><span class=\"s2\">people</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"dl\">\"</span><span class=\"s2\">organizations</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"dl\">\"</span><span class=\"s2\">topics</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"dl\">\"</span><span class=\"s2\">publicationRestrictions</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n          <span class=\"p\">],</span>\n        <span class=\"p\">},</span>\n      <span class=\"p\">},</span>\n    <span class=\"p\">},</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"kd\">const</span> <span class=\"nx\">content</span> <span class=\"o\">=</span> <span class=\"nx\">response</span><span class=\"p\">.</span><span class=\"nx\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]?.</span><span class=\"nx\">message</span><span class=\"p\">.</span><span class=\"nx\">content</span><span class=\"p\">;</span>\n  <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">content</span><span class=\"p\">)</span> <span class=\"k\">throw</span> <span class=\"k\">new</span> <span class=\"nc\">Error</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">The model returned no structured content</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n  <span class=\"k\">return</span> <span class=\"nx\">JSON</span><span class=\"p\">.</span><span class=\"nf\">parse</span><span class=\"p\">(</span><span class=\"nx\">content</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"nx\">MediaRecord</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n\n<span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">splitByTokenBudget</span><span class=\"p\">(</span>\n  <span class=\"nx\">paragraphs</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[],</span>\n  <span class=\"nx\">maxTokens</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">,</span>\n  <span class=\"nx\">countTokens</span><span class=\"p\">:</span> <span class=\"nx\">CountTokens</span><span class=\"p\">,</span>\n<span class=\"p\">):</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"kr\">string</span><span class=\"p\">[]</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"na\">chunks</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[]</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n  <span class=\"kd\">let</span> <span class=\"nx\">current</span> <span class=\"o\">=</span> <span class=\"dl\">\"\"</span><span class=\"p\">;</span>\n\n  <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">paragraph</span> <span class=\"k\">of</span> <span class=\"nx\">paragraphs</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">candidate</span> <span class=\"o\">=</span> <span class=\"nx\">current</span> <span class=\"p\">?</span> <span class=\"s2\">`</span><span class=\"p\">${</span><span class=\"nx\">current</span><span class=\"p\">}</span><span class=\"s2\">\\n\\n</span><span class=\"p\">${</span><span class=\"nx\">paragraph</span><span class=\"p\">}</span><span class=\"s2\">`</span> <span class=\"p\">:</span> <span class=\"nx\">paragraph</span><span class=\"p\">;</span>\n    <span class=\"k\">if </span><span class=\"p\">((</span><span class=\"k\">await</span> <span class=\"nf\">countTokens</span><span class=\"p\">(</span><span class=\"nx\">candidate</span><span class=\"p\">))</span> <span class=\"o\">&lt;=</span> <span class=\"nx\">maxTokens</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"nx\">current</span> <span class=\"o\">=</span> <span class=\"nx\">candidate</span><span class=\"p\">;</span>\n      <span class=\"k\">continue</span><span class=\"p\">;</span>\n    <span class=\"p\">}</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">current</span><span class=\"p\">)</span> <span class=\"nx\">chunks</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nx\">current</span><span class=\"p\">);</span>\n    <span class=\"k\">if </span><span class=\"p\">((</span><span class=\"k\">await</span> <span class=\"nf\">countTokens</span><span class=\"p\">(</span><span class=\"nx\">paragraph</span><span class=\"p\">))</span> <span class=\"o\">&gt;</span> <span class=\"nx\">maxTokens</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"k\">throw</span> <span class=\"k\">new</span> <span class=\"nc\">Error</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">Split this paragraph at tokenizer-aware sentence boundaries</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n    <span class=\"p\">}</span>\n    <span class=\"nx\">current</span> <span class=\"o\">=</span> <span class=\"nx\">paragraph</span><span class=\"p\">;</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">current</span><span class=\"p\">)</span> <span class=\"nx\">chunks</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nx\">current</span><span class=\"p\">);</span>\n  <span class=\"k\">return</span> <span class=\"nx\">chunks</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"kd\">function</span> <span class=\"nf\">mergeUnique</span><span class=\"p\">(</span><span class=\"nx\">parts</span><span class=\"p\">:</span> <span class=\"nx\">MediaRecord</span><span class=\"p\">[]):</span> <span class=\"nx\">MediaRecord</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">values</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">field</span><span class=\"p\">:</span> <span class=\"kr\">keyof</span> <span class=\"nx\">MediaRecord</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span>\n    <span class=\"p\">[...</span><span class=\"k\">new</span> <span class=\"nc\">Set</span><span class=\"p\">(</span><span class=\"nx\">parts</span><span class=\"p\">.</span><span class=\"nf\">flatMap</span><span class=\"p\">((</span><span class=\"nx\">part</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">part</span><span class=\"p\">[</span><span class=\"nx\">field</span><span class=\"p\">]).</span><span class=\"nf\">map</span><span class=\"p\">((</span><span class=\"nx\">v</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">trim</span><span class=\"p\">()))]</span>\n      <span class=\"p\">.</span><span class=\"nf\">filter</span><span class=\"p\">(</span><span class=\"nb\">Boolean</span><span class=\"p\">);</span>\n\n  <span class=\"k\">return</span> <span class=\"p\">{</span>\n    <span class=\"na\">people</span><span class=\"p\">:</span> <span class=\"nf\">values</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">people</span><span class=\"dl\">\"</span><span class=\"p\">),</span>\n    <span class=\"na\">organizations</span><span class=\"p\">:</span> <span class=\"nf\">values</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">organizations</span><span class=\"dl\">\"</span><span class=\"p\">),</span>\n    <span class=\"na\">topics</span><span class=\"p\">:</span> <span class=\"nf\">values</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">topics</span><span class=\"dl\">\"</span><span class=\"p\">),</span>\n    <span class=\"na\">publicationRestrictions</span><span class=\"p\">:</span> <span class=\"nf\">values</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">publicationRestrictions</span><span class=\"dl\">\"</span><span class=\"p\">),</span>\n  <span class=\"p\">};</span>\n<span class=\"p\">}</span>\n\n<span class=\"k\">export</span> <span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">extractMediaRecord</span><span class=\"p\">(</span>\n  <span class=\"nb\">document</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">,</span>\n  <span class=\"nx\">countTokens</span><span class=\"p\">:</span> <span class=\"nx\">CountTokens</span><span class=\"p\">,</span>\n  <span class=\"nx\">embed</span><span class=\"p\">:</span> <span class=\"nx\">Embed</span><span class=\"p\">,</span>\n  <span class=\"nx\">rerank</span><span class=\"p\">:</span> <span class=\"nx\">Rerank</span><span class=\"p\">,</span>\n  <span class=\"nx\">extract</span><span class=\"p\">:</span> <span class=\"nb\">Extract</span><span class=\"p\">,</span>\n<span class=\"p\">):</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nx\">MediaRecord</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">paragraphs</span> <span class=\"o\">=</span> <span class=\"nb\">document</span><span class=\"p\">.</span><span class=\"nf\">split</span><span class=\"p\">(</span><span class=\"sr\">/</span><span class=\"se\">\\n\\s</span><span class=\"sr\">*</span><span class=\"se\">\\n</span><span class=\"sr\">/</span><span class=\"p\">).</span><span class=\"nf\">filter</span><span class=\"p\">(</span><span class=\"nb\">Boolean</span><span class=\"p\">);</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">chunks</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">splitByTokenBudget</span><span class=\"p\">(</span><span class=\"nx\">paragraphs</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"nx\">_000</span><span class=\"p\">,</span> <span class=\"nx\">countTokens</span><span class=\"p\">);</span>\n\n  <span class=\"k\">await</span> <span class=\"nf\">embed</span><span class=\"p\">(</span><span class=\"nx\">chunks</span><span class=\"p\">);</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">queries</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"dl\">\"</span><span class=\"s2\">exact names of people</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n    <span class=\"dl\">\"</span><span class=\"s2\">exact names of organizations</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n    <span class=\"dl\">\"</span><span class=\"s2\">main editorial topics</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n    <span class=\"dl\">\"</span><span class=\"s2\">embargoes, rights, or publication restrictions</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n  <span class=\"p\">];</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">selected</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nb\">Set</span><span class=\"o\">&lt;</span><span class=\"kr\">number</span><span class=\"o\">&gt;</span><span class=\"p\">();</span>\n\n  <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">query</span> <span class=\"k\">of</span> <span class=\"nx\">queries</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">ranked</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">withRateLimitRetry</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nf\">rerank</span><span class=\"p\">(</span><span class=\"nx\">query</span><span class=\"p\">,</span> <span class=\"nx\">chunks</span><span class=\"p\">));</span>\n    <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">item</span> <span class=\"k\">of</span> <span class=\"nx\">ranked</span><span class=\"p\">.</span><span class=\"nf\">slice</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">4</span><span class=\"p\">))</span> <span class=\"nx\">selected</span><span class=\"p\">.</span><span class=\"nf\">add</span><span class=\"p\">(</span><span class=\"nx\">item</span><span class=\"p\">.</span><span class=\"nx\">index</span><span class=\"p\">);</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"kd\">const</span> <span class=\"na\">parts</span><span class=\"p\">:</span> <span class=\"nx\">MediaRecord</span><span class=\"p\">[]</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n  <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">index</span> <span class=\"k\">of</span> <span class=\"p\">[...</span><span class=\"nx\">selected</span><span class=\"p\">].</span><span class=\"nf\">sort</span><span class=\"p\">((</span><span class=\"nx\">a</span><span class=\"p\">,</span> <span class=\"nx\">b</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">a</span> <span class=\"o\">-</span> <span class=\"nx\">b</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n    <span class=\"nx\">parts</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"k\">await</span> <span class=\"nf\">withRateLimitRetry</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nf\">extract</span><span class=\"p\">(</span><span class=\"nx\">chunks</span><span class=\"p\">[</span><span class=\"nx\">index</span><span class=\"p\">])));</span>\n  <span class=\"p\">}</span>\n  <span class=\"k\">return</span> <span class=\"nf\">mergeUnique</span><span class=\"p\">(</span><span class=\"nx\">parts</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n\n<span class=\"k\">void</span> <span class=\"nx\">extract</span><span class=\"p\">;</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The adapters are intentional. Token counting belongs before dispatch. Embeddings produce candidates; reranking orders those candidates against a specific field question. The extraction adapter should require the same JSON schema for every selected chunk and surface non-success responses rather than assuming a valid result. A provider SDK may perform the underlying POST and Bearer authentication, while the application still owns retry and validation policy.</p>\n\n<p>There is one sharp edge worth calling out. A single paragraph can exceed the budget. The sample rejects it so the condition cannot slip through silently; a production splitter should recurse through sentences using tokenizer-aware boundaries. Character slicing looks convenient, but characters are not tokens, and a blind cut can separate a name from the sentence that explains its role. For a concrete starting point, this example caps a chunk at 2,000 tokens, selects four passages per field query, and stops after four attempts. Those numbers are tunable controls, not performance claims. The trade-off is recall versus bounded work: shrinking either selection number can speed the job while making a distant correction easier to miss.</p>\n\n<p>Test the miss, not the mood.</p>\n\n<h2>\n  \n  \n  Make correctness measurable before measuring speed\n</h2>\n\n<p>Record stage-level facts: source document ID, input token count, chunk count, candidate count, reranked count, extraction attempts, schema-validation outcome, and merge conflicts. Do not log the private transcript or extracted personal data by default. Correlation needs identifiers, not content.</p>\n\n<p>Separate latency for counting, embedding, reranking, extraction, and merging. A rise in rerank duration points to a different boundary than a rise in schema-invalid fragments. One is about selection work or provider behavior; the other is about schema adherence, prompts, or conflicting evidence. A single <code>ai_request_duration</code> metric erases that distinction.</p>\n\n<p>Three ratios are especially useful for alerting: timed-out documents per completed document, invalid fragments per extraction attempt, and records with unresolved conflicts per import batch. Alert on a sustained rate instead of one slow call. Include a batch ID and request ID so an operator can move from an aggregate signal to one job without searching source text.</p>\n\n<p>My default decision rule is conservative: deduplicate low-risk topic labels, but send conflicting embargoes or publication restrictions to review. Last-write-wins is fast. It is also the wrong merge policy when two selected passages disagree about whether material may be published. The extra review load buys a clear correctness boundary.</p>\n\n<p>This is the crisp before and after: before, a timeout says “the model was slow.” After, telemetry says “18 chunks were created, four passages were selected for restrictions, three fragments validated, and one conflict needs review.” The second message gives an engineer somewhere to act without claiming a latency benchmark that was never measured.</p>\n\n<h2>\n  \n  \n  Which runtime boundary fits this pipeline?\n</h2>\n\n<p>The algorithm is portable. Operational ownership is not. Compare options by where credentials, routing, upgrades, and evidence telemetry should live.</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Option</th>\n<th>Good fit</th>\n<th>Boundary you still own</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>OpenAI direct</td>\n<td>A team standardizing on OpenAI's API and structured-output surface</td>\n<td>Cross-provider routing and the rest of the retrieval pipeline remain application concerns</td>\n</tr>\n<tr>\n<td>Anthropic direct</td>\n<td>A team choosing Anthropic's API as its direct extraction boundary</td>\n<td>Embedding, reranking, and cross-provider policy require separate decisions</td>\n</tr>\n<tr>\n<td>AWS Bedrock</td>\n<td>An organization that wants model access governed inside its AWS environment</td>\n<td>Integration and access policy follow the AWS operating model</td>\n</tr>\n<tr>\n<td>LiteLLM</td>\n<td>A team willing to operate an open-source gateway and control its deployment</td>\n<td>Gateway upgrades, capacity, and availability become team responsibilities</td>\n</tr>\n<tr>\n<td>Infrai</td>\n<td>A team consolidating backend integrations under one credential and bill</td>\n<td>Field-specific evidence selection and merge rules still belong in application code</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>No row removes the need to validate model output. Direct providers reduce intermediaries. Bedrock can fit an established AWS control plane. LiteLLM gives teams source-visible gateway control, with the corresponding operational work. These are meaningful differences, not a ranking disguised as a table.</p>\n\n<p>Infrai is relevant when credential and invoice sprawl extends beyond the model call: one key and one bill cover backend services, so an extraction worker does not need a growing set of service credentials and month-end invoices. Infrai also exposes backend capabilities through a single API over pure HTTP, without installing an SDK; any language and any runtime that can send a request can use the same consistent interface. That matters when a media importer starts in Node.js but a batch worker later moves elsewhere, because the integration contract and vendor-routing code do not need to change. Its public API discovery is genuinely self-describing and requires no key. Discovery reports request and response schemas, billing metadata, readiness, and runnable examples; every documented capability has examples in 10 languages. That reduces adapter guesswork when a TypeScript worker combines counting, reranking, and model calls. The verified snapshot contains 295 routes across 20 modules.</p>\n\n<p>Those operational conveniences do not make retrieval correct. They reduce integration friction. The schema, field queries, evidence retention, and conflict policy remain yours.</p>\n\n<h2>\n  \n  \n  Why not send every chunk to the model?\n</h2>\n\n<p>For a small corpus, sending every chunk may be the clearest first implementation. It avoids retrieval misses and gives you a baseline against which to evaluate selection. The cost is more extraction calls, more duplicated evidence, and a larger merge surface. Do not add embeddings and reranking merely because the architecture sounds sophisticated.</p>\n\n<p>For long archives, retrieval earns its place when field-specific selection reduces irrelevant text without losing required evidence. Test that claim with a labeled set. Measure field recall before celebrating fewer model calls, because a fast pipeline that omits the final-page embargo is incorrect.</p>\n\n<p>Another objection is that chunking destroys context. It can. Use modest overlap where sentences cross boundaries, keep stable chunk identifiers, and preserve evidence references with each extracted fragment. If a field depends on relationships across distant sections, retrieve multiple passages for that field and let the schema-bound extraction step see them together. The answer is controlled context, not automatically more context.</p>\n\n<p>Finally, retries need a narrow meaning. Retry 429 responses with exponential backoff and honor <code>Retry-After</code> when present. Surface authorization, schema, and other client errors immediately. For batch submission or any create operation, use a client-supplied identifier or idempotency key so a retry cannot create duplicate work. HTTP semantics do not make arbitrary POST retries safe on their own.</p>\n\n<p>The practical decision is straightforward: use synchronous extraction while documents reliably fit the token and request budgets; move imports to batch processing as duration and volume grow; add retrieval when labeled evaluation shows that it preserves the fields you care about. Keep every stage observable. Correct JSON is the target, and lower latency only counts when the evidence survives.</p>\n\n<h2>\n  \n  \n  References\n</h2>\n\n<ul>\n<li><a href=\"https://platform.openai.com/docs/guides/structured-outputs\" rel=\"noopener noreferrer\">OpenAI structured outputs</a></li>\n<li><a href=\"https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview\" rel=\"noopener noreferrer\">Anthropic tool use</a></li>\n<li><a href=\"https://docs.aws.amazon.com/bedrock/\" rel=\"noopener noreferrer\">Amazon Bedrock documentation</a></li>\n<li><a href=\"https://github.com/BerriAI/litellm\" rel=\"noopener noreferrer\">LiteLLM open-source gateway</a></li>\n<li><a href=\"https://www.rfc-editor.org/rfc/rfc9110\" rel=\"noopener noreferrer\">RFC 9110: HTTP Semantics</a></li>\n</ul>","score":5},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"I Built a Production-Ready Dashboard Template with Quasar, Vue 3 and TypeScript","link":"https://dev.to/patrickmonteiro/i-built-a-production-ready-dashboard-template-with-quasar-vue-3-and-typescript-2339","pubDate":"Tue, 22 Sep 2026 13:31:50 +0000","description":"<p>Building a modern web application from scratch often means spending a lot of time on things that are not actually part of the product you want to build.</p>\n\n<p>Authentication layouts.</p>\n\n<p>Navigation.</p>\n\n<p>Dashboards.</p>\n\n<p>Tables.</p>\n\n<p>Charts.</p>\n\n<p>Responsive layouts.</p>\n\n<p>Dark mode.</p>\n\n<p>Reusable components.</p>\n\n<p>And, of course, making everything look good.</p>\n\n<p>I wanted to solve that problem for developers working with <strong>Quasar Framework</strong>.</p>\n\n<p>So I decided to revive a project that has been sitting on GitHub for more than five years and completely redesign it for the modern Quasar ecosystem.</p>\n\n<p>Meet <strong>Quasar Dashboard PRO</strong>.</p>\n\n<blockquote>\n<p>A modern, production-ready dashboard template built with Quasar Framework, Vue 3, TypeScript and Vite.</p>\n</blockquote>\n\n<p><a href=\"https://github.com/patrickmonteiro/quasar-dashboard-pro\" rel=\"noopener noreferrer\">View the project on GitHub</a></p>\n\n\n\n\n<h2>\n  \n  \n  Why another dashboard template?\n</h2>\n\n<p>There are countless dashboard templates available for React, Next.js and other popular ecosystems.</p>\n\n<p>But when working with Vue and especially Quasar, I often found myself starting with a clean Quasar project and rebuilding the same application foundations over and over again.</p>\n\n<p>The goal of Quasar Dashboard PRO is simple:</p>\n\n<p><strong>Give developers a solid starting point instead of an empty project.</strong></p>\n\n<p>You should be able to clone the repository, install the dependencies, start the development server and immediately have a complete dashboard application to work from.</p>\n\n\n\n\n<h2>\n  \n  \n  Built with Quasar\n</h2>\n\n<p>The foundation of the project is <a href=\"https://quasar.dev/\" rel=\"noopener noreferrer\">Quasar Framework</a>.</p>\n\n<p>And this is one of the reasons I chose Quasar in the first place.</p>\n\n<p>Quasar is not just a UI component library.</p>\n\n<p>It provides a complete application framework around Vue, with tooling for different application targets.</p>\n\n<p>The current Quasar ecosystem supports applications such as:</p>\n\n<ul>\n<li>SPA</li>\n<li>SSR</li>\n<li>SSG</li>\n<li>PWA</li>\n<li>Mobile applications</li>\n<li>Desktop applications</li>\n<li>Browser extensions</li>\n</ul>\n\n<p>All from the same Vue ecosystem.</p>\n\n<p>That means the dashboard you start building today doesn't necessarily have to remain a browser-only application tomorrow.</p>\n\n\n\n\n<h2>\n  \n  \n  What's inside?\n</h2>\n\n<p>Quasar Dashboard PRO was designed around real application requirements rather than just a collection of UI screenshots.</p>\n\n<p>The dashboard includes several application areas to demonstrate how the architecture can be used in real-world projects.</p>\n\n<h2>\n  \n  \n  Finance\n</h2>\n\n<p>A complete finance dashboard example with:</p>\n\n<ul>\n<li>Multiple account cards</li>\n<li>Income and expenditure charts</li>\n<li>Spending limits</li>\n<li>Savings goals</li>\n<li>Currency-based accounts</li>\n<li>Responsive layouts</li>\n</ul>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bu4rzcpelx36tfhbshx.png\" class=\"article-body-image-wrapper\"><img src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bu4rzcpelx36tfhbshx.png\" alt=\"Finance\" width=\"799\" height=\"453\"></a></p>\n\n\n\n\n<h2>\n  \n  \n  CRM\n</h2>\n\n<p>The template also includes a CRM-oriented structure with areas such as:</p>\n\n<ul>\n<li>Leads</li>\n<li>Deals</li>\n<li>Customers</li>\n<li>Communication</li>\n<li>Overview dashboards</li>\n</ul>\n\n<p>This makes it easier to adapt the project for SaaS applications, internal tools and business platforms.</p>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcko375wvn98trzwdichm.png\" class=\"article-body-image-wrapper\"><img src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcko375wvn98trzwdichm.png\" alt=\"CRM\" width=\"799\" height=\"452\"></a></p>\n\n\n\n\n<h2>\n  \n  \n  E-commerce\n</h2>\n\n<p>The project includes examples for:</p>\n\n<ul>\n<li>Products</li>\n<li>Shopping cart</li>\n<li>Checkout</li>\n<li>Order history</li>\n<li>Order summaries</li>\n</ul>\n\n<p>The goal isn't to create a complete e-commerce platform.</p>\n\n<p>Instead, these modules demonstrate how different business domains can coexist inside the same dashboard architecture.</p>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi07132ulr9epwuydfr5v.png\" class=\"article-body-image-wrapper\"><img src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi07132ulr9epwuydfr5v.png\" alt=\"Ecommerce\" width=\"800\" height=\"455\"></a></p>\n\n\n\n\n<h2>\n  \n  \n  Fleet Tracking\n</h2>\n\n<p>One of the most interesting parts of the new version is the fleet tracking dashboard.</p>\n\n<p>It demonstrates how Quasar can be used to build more complex operational interfaces.</p>\n\n<p>The dashboard includes:</p>\n\n<ul>\n<li>Live vehicle monitoring</li>\n<li>Vehicle status</li>\n<li>Trip history</li>\n<li>Alerts</li>\n<li>Maintenance</li>\n<li>Map-based visualization</li>\n<li>Risk zones</li>\n<li>Real-time metrics</li>\n</ul>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi22276bqj2dgdnehhz5i.png\" class=\"article-body-image-wrapper\"><img src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi22276bqj2dgdnehhz5i.png\" alt=\"Fleet\" width=\"800\" height=\"446\"></a></p>\n\n<p>This type of interface is particularly interesting for logistics, transportation, field service and IoT applications.</p>\n\n\n\n\n<h2>\n  \n  \n  A reusable application foundation\n</h2>\n\n<p>The goal isn't just to provide a nice-looking dashboard.</p>\n\n<p>The project is structured to provide reusable foundations for applications.</p>\n\n<p>Instead of starting with:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>src/\n  components/\n  pages/\n  layouts/\n</code></pre>\n\n</div>\n\n\n\n<p>and then spending days deciding how everything should fit together, you start with an application that already has:</p>\n\n<p>Navigation<br>\nLayouts<br>\nDashboard pages<br>\nResponsive behavior<br>\nReusable UI patterns<br>\nTables<br>\nCards<br>\nCharts<br>\nForms<br>\nApplication sections<br>\nTheme support</p>\n\n<p>You can then remove what you don't need and start building your actual product.</p>\n<h2>\n  \n  \n  Responsive by design\n</h2>\n\n<p>Modern dashboards have to work across different screen sizes.</p>\n\n<p>A dashboard that looks great on a large monitor but becomes unusable on a laptop or mobile device isn't really production-ready.</p>\n\n<p>Quasar's responsive utilities and component system make it possible to build these interfaces while keeping the development experience consistent.</p>\n\n<p>The goal with Quasar Dashboard PRO is to treat responsive behavior as part of the architecture rather than something added at the end.</p>\n<h2>\n  \n  \n  TypeScript\n</h2>\n\n<p>The project is built with TypeScript.</p>\n\n<p>This is particularly important for dashboard applications because they usually contain a lot of structured data.</p>\n\n<p>For example:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kr\">interface</span> <span class=\"nx\">Transaction</span> <span class=\"p\">{</span>\n  <span class=\"nl\">id</span><span class=\"p\">:</span> <span class=\"kr\">number</span>\n  <span class=\"nx\">description</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"nx\">amount</span><span class=\"p\">:</span> <span class=\"kr\">number</span>\n  <span class=\"nx\">currency</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"nx\">status</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">pending</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">completed</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">failed</span><span class=\"dl\">'</span>\n  <span class=\"nx\">date</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Strong typing becomes increasingly valuable as the application grows.</p>\n\n<p>It makes refactoring safer, improves editor support and makes the codebase easier to maintain.</p>\n\n<h2>\n  \n  \n  Vite\n</h2>\n\n<p>The project uses the modern Quasar + Vite tooling.</p>\n\n<p>This provides a fast development experience while keeping the project aligned with the current Vue ecosystem.</p>\n\n<p>Quasar's current tooling is built around Vite, and the framework provides its own CLI and build modes on top of it.</p>\n\n<h2>\n  \n  \n  Designed for customization\n</h2>\n\n<p>A dashboard template should not force developers into a single visual identity.</p>\n\n<p>The project is designed to be customized.</p>\n\n<p>You can change:</p>\n\n<p>Colors<br>\nTypography<br>\nNavigation<br>\nLayouts<br>\nComponents<br>\nDashboard cards<br>\nCharts<br>\nPages<br>\nApplication modules</p>\n\n<p>The idea is to provide a strong starting point while keeping the project flexible enough to become your own application.</p>\n<h2>\n  \n  \n  Open Source\n</h2>\n\n<p>Quasar Dashboard PRO is open source.</p>\n\n<p>The repository is available on GitHub:</p>\n\n<p>👉 <a href=\"https://github.com/patrickmonteiro/quasar-dashboard-pro\" rel=\"noopener noreferrer\">GitHub Repository</a></p>\n\n<p>The goal is not only to provide a ready-to-use template, but also to give the Quasar community something that can evolve over time.</p>\n\n<p>I'm especially interested in seeing what developers build with it.</p>\n\n<p><strong>Who is it for?</strong></p>\n\n<p>Quasar Dashboard PRO can be useful for developers building:</p>\n\n<p><strong>SaaS applications</strong></p>\n\n<p>Start with a complete dashboard and focus on the actual product.</p>\n\n<p><strong>Internal tools</strong></p>\n\n<p>Build admin panels and business systems faster.</p>\n\n<p><strong>CRM systems</strong></p>\n\n<p>Use the CRM structure as a starting point.</p>\n\n<p><strong>Financial applications</strong></p>\n\n<p>Use the finance dashboard as a foundation.</p>\n\n<p><strong>E-commerce platforms</strong></p>\n\n<p>Reuse the product, checkout and order structures.</p>\n\n<p><strong>Fleet and logistics systems</strong></p>\n\n<p>Use the fleet tracking dashboard as a starting point for operational applications.</p>\n\n<p><strong>Admin panels</strong></p>\n\n<p>Or simply use it as a general-purpose Quasar admin template.</p>\n<h2>\n  \n  \n  Why Quasar?\n</h2>\n\n<p>One of the biggest reasons I enjoy working with Quasar is that it allows developers to think beyond a single deployment target.</p>\n\n<p>A Vue application can evolve into different experiences without requiring an entirely different technology stack.</p>\n\n<p>For example:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>             Vue + Quasar\n                   │\n        ┌──────────┼──────────┐\n        │          │          │\n       SPA        PWA        SSR\n        │          │          │\n        └─────┬────┴────┬─────┘\n              │         │\n           Mobile    Desktop\n</code></pre>\n\n</div>\n\n\n\n<p>This is one of the biggest strengths of the framework.</p>\n\n<p>You can build a common application foundation and adapt it to different platforms when the product requires it.</p>\n\n<h2>\n  \n  \n  What's next?\n</h2>\n\n<p>The current version is only the beginning.</p>\n\n<p>Some of the things I would like to explore in future versions include:</p>\n\n<p>More dashboard examples<br>\nMore reusable components<br>\nAuthentication examples<br>\nRole-based permissions<br>\nAdvanced data tables<br>\nMore chart examples<br>\nBetter mobile experiences<br>\nPWA improvements<br>\nMore real-world application modules<br>\nAI-assisted features</p>\n\n<p>And, of course, contributions and ideas from the community.</p>\n\n<h2>\n  \n  \n  Try it yourself\n</h2>\n\n<p>If you're working with Quasar and need a modern starting point for your next project, give it a try.</p>\n\n<p>👉 View Quasar Dashboard PRO on GitHub</p>\n\n<p>If you build something with it, I'd love to see what you create.</p>\n\n<p>Star the repository, open an issue, submit a PR or just share your project.</p>\n\n<h2>\n  \n  \n  Final thoughts\n</h2>\n\n<p>I don't think developers should spend the first days of a project rebuilding the same dashboard foundations again and again.</p>\n\n<p>The interesting part is the product.</p>\n\n<p>The business logic.</p>\n\n<p>The user experience.</p>\n\n<p>The problem you're trying to solve.</p>\n\n<p>Quasar Dashboard PRO is an attempt to make that starting point faster.</p>\n\n<p>Clone it. Customize it. Build something great with Quasar.</p>","score":7},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"From a modular monolith to microservices without a rewrite","link":"https://dev.to/icebob/from-a-modular-monolith-to-microservices-without-a-rewrite-1b5a","pubDate":"Tue, 22 Sep 2026 13:00:14 +0000","description":"<p>Most \"monolith to microservices\" stories go one of two ways. Either it's a rewrite that takes eighteen months and ships a system nobody asked for, or it's a big-bang split into fifteen services that immediately need a Kubernetes team. Both come from the same mistake: treating \"process boundaries\" and \"module boundaries\" as the same decision.</p>\n\n<p>They aren't. You can draw the module boundaries now, inside the monolith, and move the process boundaries later — one service at a time, when you have a reason to. This article shows exactly that with a small Express app: three stages, the same service files in every stage, and real output at each step, including the one thing that <em>does</em> break when you finally run two copies of a service.</p>\n\n<h2>\n  \n  \n  The starting point\n</h2>\n\n<p>A perfectly ordinary Express monolith. Three modules, wired together with <code>require()</code>, one process.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// lib/orders.js — calls users and mailer through plain imports. Tight coupling, zero ceremony.</span>\n<span class=\"kd\">const</span> <span class=\"nx\">users</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">./users</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n<span class=\"kd\">const</span> <span class=\"nx\">mailer</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">./mailer</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n\n<span class=\"kd\">let</span> <span class=\"nx\">counter</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n<span class=\"kd\">const</span> <span class=\"nx\">orders</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n\n<span class=\"nx\">exports</span><span class=\"p\">.</span><span class=\"nx\">create</span> <span class=\"o\">=</span> <span class=\"p\">({</span> <span class=\"nx\">userId</span><span class=\"p\">,</span> <span class=\"nx\">item</span><span class=\"p\">,</span> <span class=\"nx\">amount</span> <span class=\"p\">})</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">user</span> <span class=\"o\">=</span> <span class=\"nx\">users</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"nx\">userId</span><span class=\"p\">);</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">order</span> <span class=\"o\">=</span> <span class=\"p\">{</span> <span class=\"na\">id</span><span class=\"p\">:</span> <span class=\"o\">++</span><span class=\"nx\">counter</span><span class=\"p\">,</span> <span class=\"nx\">userId</span><span class=\"p\">,</span> <span class=\"nx\">item</span><span class=\"p\">,</span> <span class=\"nx\">amount</span> <span class=\"p\">};</span>\n  <span class=\"nx\">orders</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nx\">order</span><span class=\"p\">);</span>\n  <span class=\"nx\">mailer</span><span class=\"p\">.</span><span class=\"nf\">send</span><span class=\"p\">(</span><span class=\"nx\">user</span><span class=\"p\">.</span><span class=\"nx\">email</span><span class=\"p\">,</span> <span class=\"s2\">`Order #</span><span class=\"p\">${</span><span class=\"nx\">order</span><span class=\"p\">.</span><span class=\"nx\">id</span><span class=\"p\">}</span><span class=\"s2\"> confirmed (</span><span class=\"p\">${</span><span class=\"nx\">item</span><span class=\"p\">}</span><span class=\"s2\">)`</span><span class=\"p\">);</span>\n  <span class=\"k\">return</span> <span class=\"nx\">order</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre>\n\n</div>\n\n\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// app.js</span>\n<span class=\"kd\">const</span> <span class=\"nx\">express</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">express</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n<span class=\"kd\">const</span> <span class=\"nx\">users</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">./lib/users</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n<span class=\"kd\">const</span> <span class=\"nx\">orders</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">./lib/orders</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">app</span> <span class=\"o\">=</span> <span class=\"nf\">express</span><span class=\"p\">();</span>\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">use</span><span class=\"p\">(</span><span class=\"nx\">express</span><span class=\"p\">.</span><span class=\"nf\">json</span><span class=\"p\">());</span>\n\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">/users/:id</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"nx\">req</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nf\">json</span><span class=\"p\">(</span><span class=\"nx\">users</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"nc\">Number</span><span class=\"p\">(</span><span class=\"nx\">req</span><span class=\"p\">.</span><span class=\"nx\">params</span><span class=\"p\">.</span><span class=\"nx\">id</span><span class=\"p\">))));</span>\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">post</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">/orders</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"nx\">req</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">userId</span> <span class=\"o\">=</span> <span class=\"nc\">Number</span><span class=\"p\">(</span><span class=\"nx\">req</span><span class=\"p\">.</span><span class=\"nf\">header</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">x-user-id</span><span class=\"dl\">\"</span><span class=\"p\">));</span>\n  <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nf\">status</span><span class=\"p\">(</span><span class=\"mi\">201</span><span class=\"p\">).</span><span class=\"nf\">json</span><span class=\"p\">(</span><span class=\"nx\">orders</span><span class=\"p\">.</span><span class=\"nf\">create</span><span class=\"p\">({</span> <span class=\"nx\">userId</span><span class=\"p\">,</span> <span class=\"p\">...</span><span class=\"nx\">req</span><span class=\"p\">.</span><span class=\"nx\">body</span> <span class=\"p\">}));</span>\n<span class=\"p\">});</span>\n\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">listen</span><span class=\"p\">(</span><span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">PORT</span> <span class=\"o\">||</span> <span class=\"mi\">3000</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>$ curl -s localhost:3000/users/1\n{\"id\":1,\"name\":\"Ada\",\"email\":\"ada@example.com\"}\n$ curl -s -X POST localhost:3000/orders -H 'content-type: application/json' -H 'x-user-id: 2' -d '{\"item\":\"Monitor\",\"amount\":329}'\n{\"id\":1,\"userId\":2,\"item\":\"Monitor\",\"amount\":329}\n</code></pre>\n\n</div>\n\n\n\n<p>Nothing wrong with this. It's the right architecture for a team of three with one deployable. The problem only starts when <code>orders</code> grows a queue consumer, <code>mailer</code> needs to scale independently because a marketing campaign sends 200k emails, and you realise nothing in the codebase says which module is allowed to call which.</p>\n\n<h2>\n  \n  \n  Stage 1: module boundaries become service boundaries — same process\n</h2>\n\n<p>Bring in a <strong>service broker</strong> (Moleculer's <code>ServiceBroker</code> — think of it as an in-process service runtime with a registry) and turn each module into a <strong>service</strong>: a plain object with a name and <strong>actions</strong> (its callable endpoints). No transporter is configured, so every call is an in-memory function call. No network, no serialization, no new infrastructure.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// services/orders.service.js — lib/orders.js as a service.</span>\n<span class=\"c1\">// No require(\"./users\") any more: the dependency goes through the broker, and is declared.</span>\n<span class=\"kd\">let</span> <span class=\"nx\">counter</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>                       <span class=\"c1\">// module-level state — fine in a monolith; see \"What breaks\" below</span>\n<span class=\"kd\">const</span> <span class=\"nx\">orders</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n\n<span class=\"nx\">module</span><span class=\"p\">.</span><span class=\"nx\">exports</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"na\">name</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">orders</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n  <span class=\"na\">dependencies</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">\"</span><span class=\"s2\">users</span><span class=\"dl\">\"</span><span class=\"p\">],</span>             <span class=\"c1\">// broker waits for `users` before starting this service</span>\n  <span class=\"na\">actions</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n    <span class=\"na\">create</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n      <span class=\"na\">params</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n        <span class=\"na\">userId</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">number</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">convert</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">},</span>\n        <span class=\"na\">item</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">string</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n        <span class=\"na\">amount</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">number</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">positive</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">},</span>\n      <span class=\"p\">},</span>\n      <span class=\"k\">async</span> <span class=\"nf\">handler</span><span class=\"p\">(</span><span class=\"nx\">ctx</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">user</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">call</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">users.get</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">id</span><span class=\"p\">:</span> <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">params</span><span class=\"p\">.</span><span class=\"nx\">userId</span> <span class=\"p\">});</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">order</span> <span class=\"o\">=</span> <span class=\"p\">{</span> <span class=\"na\">id</span><span class=\"p\">:</span> <span class=\"o\">++</span><span class=\"nx\">counter</span><span class=\"p\">,</span> <span class=\"na\">userId</span><span class=\"p\">:</span> <span class=\"nx\">user</span><span class=\"p\">.</span><span class=\"nx\">id</span><span class=\"p\">,</span> <span class=\"na\">item</span><span class=\"p\">:</span> <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">params</span><span class=\"p\">.</span><span class=\"nx\">item</span><span class=\"p\">,</span> <span class=\"na\">amount</span><span class=\"p\">:</span> <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">params</span><span class=\"p\">.</span><span class=\"nx\">amount</span> <span class=\"p\">};</span>\n        <span class=\"nx\">orders</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nx\">order</span><span class=\"p\">);</span>\n        <span class=\"k\">await</span> <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">emit</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">order.created</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"nx\">order</span><span class=\"p\">,</span> <span class=\"nx\">user</span> <span class=\"p\">});</span>\n        <span class=\"k\">return</span> <span class=\"p\">{</span> <span class=\"p\">...</span><span class=\"nx\">order</span><span class=\"p\">,</span> <span class=\"na\">servedBy</span><span class=\"p\">:</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">broker</span><span class=\"p\">.</span><span class=\"nx\">nodeID</span><span class=\"p\">,</span> <span class=\"na\">usersServedBy</span><span class=\"p\">:</span> <span class=\"nx\">user</span><span class=\"p\">.</span><span class=\"nx\">servedBy</span> <span class=\"p\">};</span>\n      <span class=\"p\">},</span>\n    <span class=\"p\">},</span>\n  <span class=\"p\">},</span>\n<span class=\"p\">};</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Three things changed and they are all improvements you'd want anyway:</p>\n\n<ul>\n<li>\n<strong>The dependency is explicit.</strong> <code>dependencies: [\"users\"]</code> is documentation the runtime enforces — <code>orders</code> won't start until <code>users</code> is available.</li>\n<li>\n<strong>The input is validated at the boundary.</strong> <code>params</code> is a schema; a bad <code>amount</code> is rejected before the handler runs.</li>\n<li>\n<strong>Mailer is no longer called — it's notified.</strong> <code>orders</code> emits <code>order.created</code>; whoever cares subscribes. That's the seam you'll use later to move it out.\n</li>\n</ul>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// services/mailer.service.js — lib/mailer.js as a service. Instead of being called, it reacts to an event.</span>\n<span class=\"nx\">module</span><span class=\"p\">.</span><span class=\"nx\">exports</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"na\">name</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">mailer</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n  <span class=\"na\">events</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n    <span class=\"dl\">\"</span><span class=\"s2\">order.created</span><span class=\"dl\">\"</span><span class=\"p\">(</span><span class=\"nx\">ctx</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"kd\">const</span> <span class=\"p\">{</span> <span class=\"nx\">order</span><span class=\"p\">,</span> <span class=\"nx\">user</span> <span class=\"p\">}</span> <span class=\"o\">=</span> <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">params</span><span class=\"p\">;</span>\n      <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">logger</span><span class=\"p\">.</span><span class=\"nf\">info</span><span class=\"p\">(</span><span class=\"s2\">`→ </span><span class=\"p\">${</span><span class=\"nx\">user</span><span class=\"p\">.</span><span class=\"nx\">email</span><span class=\"p\">}</span><span class=\"s2\">: Order #</span><span class=\"p\">${</span><span class=\"nx\">order</span><span class=\"p\">.</span><span class=\"nx\">id</span><span class=\"p\">}</span><span class=\"s2\"> confirmed (</span><span class=\"p\">${</span><span class=\"nx\">order</span><span class=\"p\">.</span><span class=\"nx\">item</span><span class=\"p\">}</span><span class=\"s2\">)`</span><span class=\"p\">);</span>\n    <span class=\"p\">},</span>\n  <span class=\"p\">},</span>\n<span class=\"p\">};</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Express stays. The routes call the broker instead of the modules:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// app.js — the same Express app, now with a broker inside. Routes call services instead of modules.</span>\n<span class=\"kd\">const</span> <span class=\"nx\">express</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">express</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n<span class=\"kd\">const</span> <span class=\"p\">{</span> <span class=\"nx\">ServiceBroker</span> <span class=\"p\">}</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">moleculer</span><span class=\"dl\">\"</span><span class=\"p\">);</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">broker</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">ServiceBroker</span><span class=\"p\">({</span>\n  <span class=\"na\">nodeID</span><span class=\"p\">:</span> <span class=\"s2\">`app-</span><span class=\"p\">${</span><span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">pid</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">,</span>\n  <span class=\"na\">transporter</span><span class=\"p\">:</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">TRANSPORTER</span> <span class=\"o\">||</span> <span class=\"kc\">null</span><span class=\"p\">,</span>     <span class=\"c1\">// null = local bus, in-process only</span>\n  <span class=\"na\">logger</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">Console</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">options</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">level</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">info</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">formatter</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">short</span><span class=\"dl\">\"</span> <span class=\"p\">}</span> <span class=\"p\">},</span>\n<span class=\"p\">});</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">wanted</span> <span class=\"o\">=</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">SERVICES</span> <span class=\"o\">===</span> <span class=\"kc\">undefined</span> <span class=\"p\">?</span> <span class=\"p\">[</span><span class=\"dl\">\"</span><span class=\"s2\">users</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"dl\">\"</span><span class=\"s2\">orders</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"dl\">\"</span><span class=\"s2\">mailer</span><span class=\"dl\">\"</span><span class=\"p\">]</span> <span class=\"p\">:</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">SERVICES</span><span class=\"p\">.</span><span class=\"nf\">split</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">,</span><span class=\"dl\">\"</span><span class=\"p\">).</span><span class=\"nf\">filter</span><span class=\"p\">(</span><span class=\"nb\">Boolean</span><span class=\"p\">);</span>\n<span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">name</span> <span class=\"k\">of</span> <span class=\"nx\">wanted</span><span class=\"p\">)</span> <span class=\"nx\">broker</span><span class=\"p\">.</span><span class=\"nf\">createService</span><span class=\"p\">(</span><span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"s2\">`./services/</span><span class=\"p\">${</span><span class=\"nx\">name</span><span class=\"p\">}</span><span class=\"s2\">.service.js`</span><span class=\"p\">));</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">app</span> <span class=\"o\">=</span> <span class=\"nf\">express</span><span class=\"p\">();</span>\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">use</span><span class=\"p\">(</span><span class=\"nx\">express</span><span class=\"p\">.</span><span class=\"nf\">json</span><span class=\"p\">());</span>\n\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">/users/:id</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"k\">async </span><span class=\"p\">(</span><span class=\"nx\">req</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">,</span> <span class=\"nx\">next</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"k\">try</span> <span class=\"p\">{</span> <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nf\">json</span><span class=\"p\">(</span><span class=\"k\">await</span> <span class=\"nx\">broker</span><span class=\"p\">.</span><span class=\"nf\">call</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">users.get</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">id</span><span class=\"p\">:</span> <span class=\"nx\">req</span><span class=\"p\">.</span><span class=\"nx\">params</span><span class=\"p\">.</span><span class=\"nx\">id</span> <span class=\"p\">}));</span> <span class=\"p\">}</span> <span class=\"k\">catch </span><span class=\"p\">(</span><span class=\"nx\">e</span><span class=\"p\">)</span> <span class=\"p\">{</span> <span class=\"nf\">next</span><span class=\"p\">(</span><span class=\"nx\">e</span><span class=\"p\">);</span> <span class=\"p\">}</span>\n<span class=\"p\">});</span>\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">post</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">/orders</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"k\">async </span><span class=\"p\">(</span><span class=\"nx\">req</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">,</span> <span class=\"nx\">next</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"k\">try</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">userId</span> <span class=\"o\">=</span> <span class=\"nc\">Number</span><span class=\"p\">(</span><span class=\"nx\">req</span><span class=\"p\">.</span><span class=\"nf\">header</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">x-user-id</span><span class=\"dl\">\"</span><span class=\"p\">));</span>\n    <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nf\">status</span><span class=\"p\">(</span><span class=\"mi\">201</span><span class=\"p\">).</span><span class=\"nf\">json</span><span class=\"p\">(</span><span class=\"k\">await</span> <span class=\"nx\">broker</span><span class=\"p\">.</span><span class=\"nf\">call</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">orders.create</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"nx\">userId</span><span class=\"p\">,</span> <span class=\"p\">...</span><span class=\"nx\">req</span><span class=\"p\">.</span><span class=\"nx\">body</span> <span class=\"p\">}));</span>\n  <span class=\"p\">}</span> <span class=\"k\">catch </span><span class=\"p\">(</span><span class=\"nx\">e</span><span class=\"p\">)</span> <span class=\"p\">{</span> <span class=\"nf\">next</span><span class=\"p\">(</span><span class=\"nx\">e</span><span class=\"p\">);</span> <span class=\"p\">}</span>\n<span class=\"p\">});</span>\n<span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">use</span><span class=\"p\">((</span><span class=\"nx\">err</span><span class=\"p\">,</span> <span class=\"nx\">req</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">,</span> <span class=\"nx\">next</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nf\">status</span><span class=\"p\">(</span><span class=\"nx\">err</span><span class=\"p\">.</span><span class=\"nx\">code</span> <span class=\"o\">||</span> <span class=\"mi\">500</span><span class=\"p\">).</span><span class=\"nf\">json</span><span class=\"p\">({</span> <span class=\"na\">error</span><span class=\"p\">:</span> <span class=\"nx\">err</span><span class=\"p\">.</span><span class=\"nx\">name</span><span class=\"p\">,</span> <span class=\"na\">message</span><span class=\"p\">:</span> <span class=\"nx\">err</span><span class=\"p\">.</span><span class=\"nx\">message</span> <span class=\"p\">}));</span>\n\n<span class=\"nx\">broker</span><span class=\"p\">.</span><span class=\"nf\">start</span><span class=\"p\">().</span><span class=\"nf\">then</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"nx\">app</span><span class=\"p\">.</span><span class=\"nf\">listen</span><span class=\"p\">(</span><span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">PORT</span> <span class=\"o\">||</span> <span class=\"mi\">3000</span><span class=\"p\">,</span> <span class=\"p\">()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">broker</span><span class=\"p\">.</span><span class=\"nx\">logger</span><span class=\"p\">.</span><span class=\"nf\">info</span><span class=\"p\">(</span><span class=\"s2\">`HTTP on </span><span class=\"p\">${</span><span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">PORT</span> <span class=\"o\">||</span> <span class=\"mi\">3000</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">));</span>\n<span class=\"p\">});</span>\n</code></pre>\n\n</div>\n\n\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>$ node app.js\n[20:06:31.508Z] INFO  BROKER: Node ID: app-3470689\n[20:06:31.549Z] INFO  ORDERS: Waiting for service(s) 'users'...\n[20:06:31.557Z] INFO  USERS: Service 'users' started.\n[20:06:31.557Z] INFO  MAILER: Service 'mailer' started.\n[20:06:32.552Z] INFO  ORDERS: Service(s) 'users' are available.\n[20:06:32.554Z] INFO  ORDERS: Service 'orders' started.\n[20:06:32.554Z] INFO  BROKER: ✔ ServiceBroker with 4 service(s) started successfully in 1s.\n[20:06:32.557Z] INFO  BROKER: HTTP on 3000\n\n$ curl -s localhost:3000/users/1\n{\"id\":1,\"name\":\"Ada\",\"email\":\"ada@example.com\",\"servedBy\":\"app-3470689\"}\n$ curl -s -X POST localhost:3000/orders -H 'content-type: application/json' -H 'x-user-id: 2' -d '{\"item\":\"Monitor\",\"amount\":329}'\n{\"id\":1,\"userId\":2,\"item\":\"Monitor\",\"amount\":329,\"servedBy\":\"app-3470689\",\"usersServedBy\":\"app-3470689\"}\n# app log:\n[20:06:34.088Z] INFO  MAILER: → linus@example.com: Order #1 confirmed (Monitor)\n</code></pre>\n\n</div>\n\n\n\n<p>Same behaviour, same single process, same deploy. <code>servedBy</code> and <code>usersServedBy</code> are the same node because everything is local. You could stop here for a year and still be better off than before: the boundaries are real, the contracts are validated, and nobody can sneak a <code>require(\"../orders/db\")</code> across modules any more.</p>\n\n<p>This is what \"modular monolith\" should mean in practice — not a folder convention, but boundaries a runtime knows about.</p>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1y0rll2iduos8himh70x.png\" class=\"article-body-image-wrapper\"><img src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1y0rll2iduos8himh70x.png\" alt=\"Same three service files at every stage. Only the deployment changes.\" width=\"800\" height=\"400\"></a></p>\n\n<h2>\n  \n  \n  Stage 2: extract one service — the one that actually needs it\n</h2>\n\n<p>Marketing week: <code>mailer</code> needs to scale on its own and must not slow down the request path. So move <em>only</em> <code>mailer</code> out.</p>\n\n<p>Two changes, neither of them in service code. First, give the app a <strong>transporter</strong> (a message broker — NATS here) and tell it which services to host locally:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>$ TRANSPORTER=nats://localhost:4222 SERVICES=users,orders node app.js\n</code></pre>\n\n</div>\n\n\n\n<p>Second, start <code>mailer</code> in its own process with <code>moleculer-runner</code>, the framework's CLI service host, pointed at the same <code>services/</code> folder:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// moleculer.config.js — for services that run OUTSIDE the app process, via moleculer-runner.</span>\n<span class=\"nx\">module</span><span class=\"p\">.</span><span class=\"nx\">exports</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"na\">nodeID</span><span class=\"p\">:</span> <span class=\"s2\">`svc-</span><span class=\"p\">${</span><span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">SERVICES</span> <span class=\"o\">||</span> <span class=\"dl\">\"</span><span class=\"s2\">all</span><span class=\"dl\">\"</span><span class=\"p\">}</span><span class=\"s2\">-</span><span class=\"p\">${</span><span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">pid</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">,</span>\n  <span class=\"na\">transporter</span><span class=\"p\">:</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">TRANSPORTER</span> <span class=\"o\">||</span> <span class=\"dl\">\"</span><span class=\"s2\">nats://localhost:4222</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n  <span class=\"na\">logger</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">Console</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">options</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">level</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">info</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">formatter</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">short</span><span class=\"dl\">\"</span> <span class=\"p\">}</span> <span class=\"p\">},</span>\n<span class=\"p\">};</span>\n</code></pre>\n\n</div>\n\n\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>$ SERVICEDIR=services SERVICES=mailer npx moleculer-runner --config moleculer.config.js\n</code></pre>\n\n</div>\n\n\n\n<p>Now hit the same endpoints:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>$ curl -s -X POST localhost:3000/orders -H 'content-type: application/json' -H 'x-user-id: 2' -d '{\"item\":\"Monitor\",\"amount\":329}'\n{\"id\":1,\"userId\":2,\"item\":\"Monitor\",\"amount\":329,\"servedBy\":\"app-3470296\",\"usersServedBy\":\"app-3470296\"}\n\n# mailer process log:\n[20:03:06.188Z] INFO  MAILER: → linus@example.com: Order #1 confirmed (Monitor)\n</code></pre>\n\n</div>\n\n\n\n<p>Look at what happened:</p>\n\n<ul>\n<li>\n<code>mailer</code> received the <code>order.created</code> event <strong>in another process</strong>, over NATS. The <code>orders</code> code that emits it didn't change.</li>\n<li>\n<code>orders</code> → <code>users</code> is still <code>app-3470296</code> → <code>app-3470296</code>: <strong>in-process</strong>. The app is connected to NATS, but the registry's default <code>preferLocal: true</code> routes a call to a local instance whenever one exists. You extracted one service and paid the network cost for exactly one hop — the one you chose.</li>\n</ul>\n\n<p>That's the whole method. The <code>services/</code> folder is the same in both processes; <strong>which process loads which file is a deployment decision</strong>, made per environment with two environment variables. Your dev laptop runs <code>node app.js</code> with everything local; staging runs the hybrid; production splits further. Same code.</p>\n\n<h2>\n  \n  \n  Stage 3: split the rest, scale one — and meet the thing that breaks\n</h2>\n\n<p>Let's go all the way: <code>users</code>, <code>orders</code> and <code>mailer</code> each in their own process, <strong>two</strong> instances of <code>orders</code> because it's the hot path, and the app hosting nothing but HTTP:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>$ SERVICEDIR=services SERVICES=users  npx moleculer-runner --config moleculer.config.js\n$ SERVICEDIR=services SERVICES=orders npx moleculer-runner --config moleculer.config.js\n$ SERVICEDIR=services SERVICES=orders npx moleculer-runner --config moleculer.config.js\n$ SERVICEDIR=services SERVICES=mailer npx moleculer-runner --config moleculer.config.js\n$ TRANSPORTER=nats://localhost:4222 SERVICES= node app.js\n\n$ for i in 1 2 3 4; do curl -s -X POST localhost:3000/orders -H 'content-type: application/json' -H 'x-user-id: 1' -d '{\"item\":\"Cable\",\"amount\":9}'; echo; done\n{\"id\":1,\"userId\":1,\"item\":\"Cable\",\"amount\":9,\"servedBy\":\"svc-orders-3470323\",\"usersServedBy\":\"svc-users-3470321\"}\n{\"id\":1,\"userId\":1,\"item\":\"Cable\",\"amount\":9,\"servedBy\":\"svc-orders-3470322\",\"usersServedBy\":\"svc-users-3470321\"}\n{\"id\":2,\"userId\":1,\"item\":\"Cable\",\"amount\":9,\"servedBy\":\"svc-orders-3470323\",\"usersServedBy\":\"svc-users-3470321\"}\n{\"id\":2,\"userId\":1,\"item\":\"Cable\",\"amount\":9,\"servedBy\":\"svc-orders-3470322\",\"usersServedBy\":\"svc-users-3470321\"}\n</code></pre>\n\n</div>\n\n\n\n<p>The good news: round-robin load balancing between the two <code>orders</code> instances, calls to <code>users</code> over the wire, no code changes — all of it just works.</p>\n\n<p>The bad news is in the first column. <strong>Order IDs 1, 1, 2, 2.</strong> Remember <code>let counter = 0</code> at the top of <code>orders.service.js</code>? Each process has its own. In the monolith it was a perfectly good ID generator; with two instances it's a duplicate-key bug that would have shipped.</p>\n\n<p>This is the honest part of the article. Turning modules into services is nearly free. Running two copies of one is where the monolith's hidden assumptions surface. Here is the list I check before scaling any service past one instance:</p>\n\n<ul>\n<li>\n<strong>Module-level state</strong> — counters, caches, \"the current batch\", <code>Map</code>s of sessions. Every instance gets its own. Move it to the database or a shared store, or make it instance-safe (UUIDs instead of counters; here <code>id: crypto.randomUUID()</code> is the one-line fix).</li>\n<li>\n<strong>In-memory cache.</strong> Moleculer's default <code>Memory</code> cacher is per node; with several instances you'll serve stale data from one and fresh from another. Switch the cacher to Redis — a broker option, not a code change.</li>\n<li>\n<strong>Anything that assumes ordering across requests.</strong> Two instances process concurrently; if order mattered, you were relying on a single event loop.</li>\n<li>\n<strong>Transactions across services.</strong> <code>orders</code> + <code>inventory</code> in one SQL transaction worked because they were one process on one connection. Across processes it's either a saga (compensating actions on failure) or you keep those two in the same service. Both are fine; pretending it's still atomic is not.</li>\n<li>\n<strong><code>ctx.meta</code> instead of globals.</strong> The request-scoped things you used to keep in <code>req</code> or a module variable (user ID, locale, trace ID) now need to ride along explicitly. Moleculer propagates <code>ctx.meta</code> through every call and event automatically — put them there.</li>\n</ul>\n\n<p>None of these are Moleculer problems, and none of them are avoided by any other framework; they're the actual difference between one process and two. The point of the staged approach is that you hit them <strong>one service at a time</strong>, with a working system on both sides, instead of all at once on cut-over day.</p>\n\n<h2>\n  \n  \n  The migration order that works\n</h2>\n\n<p>If I were doing this to a real codebase:</p>\n\n<ol>\n<li>\n<strong>Stage 1 for everything, no exceptions.</strong> Broker in the monolith, every module a service, every cross-module call a <code>ctx.call</code>, every fire-and-forget a <code>ctx.emit</code>. Ship it. It is a refactor with no infrastructure change, and it's where you discover which modules actually depend on which.</li>\n<li>\n<strong>Extract the asynchronous ones first.</strong> Mailer, image processing, report generation, webhooks — anything that already receives events rather than answering calls. They have no callers to break and their latency doesn't sit on the request path.</li>\n<li>\n<strong>Extract what needs to scale or deploy independently.</strong> Usually one or two hot services. Run through the \"what breaks\" list before starting the second instance.</li>\n<li>\n<strong>Leave the rest together.</strong> A modular monolith with two extracted services is a legitimate end state, not a half-finished migration. Every boundary you didn't turn into a network hop is one that can't fail at 3 a.m.</li>\n</ol>\n\n<p>And keep <code>node app.js</code> — the everything-local mode — working forever. It's how a new developer runs the whole system on a laptop with no Docker, and it's how you run integration tests without a message broker. The local bus is a feature, not a stepping stone.</p>\n\n\n\n\n<p><em>Code in this article was run on Moleculer 0.15.2, Express 5 and Node.js 22, with NATS 2 for stages 2 and 3. The <a href=\"https://github.com/moleculerjs/moleculer-examples/tree/master/04-monolith-to-microservices\" rel=\"noopener noreferrer\">moleculer-examples repository</a> has a <code>run.sh</code> that reproduces all three stages — including the duplicate-ID bug — in one go.</em></p>","score":5},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"Next.js 14 or Vue 3 + Vite? I Built the Same Game Site MVP Both Ways and Compared Them for 3 Days","link":"https://dev.to/no_momo/nextjs-14-or-vue-3-vite-i-built-the-same-game-site-mvp-both-ways-and-compared-them-for-3-days-e7c","pubDate":"Tue, 22 Sep 2026 06:51:03 +0000","description":"<h2>\n  \n  \n  TL;DR\n</h2>\n\n<p>I ended up choosing a hybrid: <strong>Next.js 14 for static content pages + a separately lazy-loaded client shell for the game runtime page</strong>.</p>\n\n<p>But that wasn't a snap decision. I spent 3 days building the same MVP twice, hit the Next.js static export dynamic route 404 trap, and dealt with Vue's SEO plugin problem. If you're making a similar decision, the process is probably more useful than the conclusion.</p>\n\n\n\n\n<h2>\n  \n  \n  Environment\n</h2>\n\n<ul>\n<li>Node 20.11 / npm 10.5</li>\n<li>Next.js 14.2.3 (App Router + <code>output: 'export'</code>)</li>\n<li>Vue 3.4 + Vite 5.2 + vue-router 4</li>\n<li>Test devices: MacBook Pro M1 (build tests), iPhone 12 (Lighthouse mobile tests)</li>\n<li>Deployment target: Vercel static hosting (zero server)</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  What Were My Constraints?\n</h2>\n\n<p>Before writing any code, I listed three hard constraints:</p>\n\n<ol>\n<li>\n<strong>SEO had to work</strong>: Most traffic for a game site comes from search engines. Game detail pages must be crawlable.</li>\n<li>\n<strong>First load had to be fast</strong>: LCP under 2.5s on mobile 4G.</li>\n<li>\n<strong>The game iframe couldn't block the main thread</strong>: While browsing the list page, no game runtime code should load.</li>\n</ol>\n\n<p>These three constraints ended up driving the entire decision.</p>\n\n\n\n\n<h2>\n  \n  \n  Day 1: Next.js Version — Static Export Taught Me a Lesson\n</h2>\n\n<h3>\n  \n  \n  What I Built\n</h3>\n\n<p>Next.js 14 App Router with this route structure:</p>\n\n<ul>\n<li>\n<code>/</code> homepage (SSG)</li>\n<li>\n<code>/games/[category]</code> category page (SSG)</li>\n<li>\n<code>/play/[id]</code> game runtime page (client-side rendered)</li>\n</ul>\n\n<p>I used <code>output: 'export'</code> for pure static export and deployed to Vercel.</p>\n\n<h3>\n  \n  \n  Trap 1: Dynamic Routes 404\n</h3>\n\n<p>After <code>next build</code>, the homepage and category pages generated HTML correctly. But <code>/play/[id]</code> all returned 404.</p>\n\n<p>After digging in: <strong>App Router with <code>output: 'export'</code> doesn't support dynamic routes by default unless you explicitly define <code>generateStaticParams</code></strong>. This is heavily discussed in the community; developers on V2EX have reported that \"the latest Next.js App Router doesn't support dynamic routes with static generation.\"</p>\n\n<p>Worse, even after defining <code>generateStaticParams</code>, all game IDs have to be pre-generated at build time. If the game count grows to hundreds, every new game triggers a full rebuild, and CI time grows exponentially.</p>\n\n<h3>\n  \n  \n  Solutions I Tried\n</h3>\n\n<p>I tried <code>@falsefoundation/next-dynamic-exports</code>, which supports dynamic routes by generating fallback pages, but it requires web server rewrite rules (Nginx <code>try_files</code>), adding deployment complexity.</p>\n\n<p>Another approach was to <strong>turn the game runtime page from a dynamic route into a client-rendered static shell</strong> — the <code>/play</code> page reads the game ID via <code>useSearchParams</code> after loading. This bypasses the <code>generateStaticParams</code> limitation, but the game runtime page is no longer prerendered, so SEO suffers. My trade-off: the game runtime page has low SEO value anyway (users arrive from the detail page), so it's acceptable.</p>\n\n<h3>\n  \n  \n  Next.js Version Data\n</h3>\n\n<ul>\n<li>Homepage LCP: 1.2s (mobile 4G throttling)</li>\n<li>First-load JS (gzip): ~85KB</li>\n<li>Build time: 12s (including 6 pre-generated game pages)</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Day 2: Vue 3 + Vite Version — Genuinely Lightweight\n</h2>\n\n<h3>\n  \n  \n  What I Built\n</h3>\n\n<p>Vue 3.4 + Vite 5.2 + vue-router 4, same route structure as the Next.js version.</p>\n\n<h3>\n  \n  \n  The Advantages Were Obvious\n</h3>\n\n<p>Vite's dev experience is excellent. Cold start under 1 second, hot updates nearly imperceptible. Build time was only 6 seconds, half of Next.js.</p>\n\n<p>First-load JS (gzip) was about 50KB, nearly 40% less than Next.js. Vue 3's runtime is lightweight by itself, and Vite's tree-shaking leaves nothing extra.</p>\n\n<h3>\n  \n  \n  But SEO Became a Problem\n</h3>\n\n<p>Vue + Vite defaults to SPA. The homepage and category pages only contain <code>&lt;div id=\"app\"&gt;&lt;/div&gt;</code> in the HTML — crawlers see no content.</p>\n\n<p>The solution was to install <code>vite-plugin-seo-prerender</code>, which prerenders the SPA into static HTML files at build time. Configuration is simple:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// vite.config.ts</span>\n<span class=\"k\">import</span> <span class=\"nx\">seoPrerender</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">vite-plugin-seo-prerender</span><span class=\"dl\">'</span>\n\n<span class=\"k\">export</span> <span class=\"k\">default</span> <span class=\"nf\">defineConfig</span><span class=\"p\">({</span>\n  <span class=\"na\">plugins</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n    <span class=\"nf\">seoPrerender</span><span class=\"p\">({</span>\n      <span class=\"na\">routes</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">/</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"dl\">'</span><span class=\"s1\">/games/puzzle</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"dl\">'</span><span class=\"s1\">/games/sports</span><span class=\"dl\">'</span><span class=\"p\">]</span>\n    <span class=\"p\">})</span>\n  <span class=\"p\">]</span>\n<span class=\"p\">})</span>\n</code></pre>\n\n</div>\n\n\n\n<p>But the plugin has a limitation: <strong>it's only suitable for generating static HTML for a small number of pages</strong>. If the game count grows to hundreds, every game detail page needs prerendering, and build time becomes uncontrollable.</p>\n\n<p>Another option is Nuxt 3, Vue's meta-framework with built-in SSG. But that introduces another framework, contradicting the \"lightweight\" goal.</p>\n\n<h3>\n  \n  \n  Vue Version Data\n</h3>\n\n<ul>\n<li>Homepage LCP: 1.5s (mobile 4G throttling, after prerendering)</li>\n<li>First-load JS (gzip): ~50KB</li>\n<li>Build time: 6s</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Day 3: The Key Finding — It's Not the Framework, It's the iframe Initialization Timing\n</h2>\n\n<p>After both versions were running, I did one thing: recorded the full timeline from homepage to clicking \"Start Game\" using Chrome DevTools Performance panel on both.</p>\n\n<p><strong>Something counterintuitive came up: the performance difference between the two versions was far smaller than I expected.</strong></p>\n\n<p>The Next.js version had 35KB more first-load JS, but on 4G that's about 200ms of download time. The real time sink while browsing the list page wasn't the framework's JS size — it was the game iframe initialization.</p>\n\n<p>In the default implementation, the iframe was initialized when the game detail page mounted. Even if the iframe <code>src</code> pointed to an empty placeholder page, creating the <code>contentWindow</code> and initializing the sandbox environment still consumed main thread time. On a Moto G Power, that single operation took about 400ms.</p>\n\n<p>The correct approach is the <strong>facade pattern</strong>: render only a lightweight static placeholder (thumbnail + play button) before the iframe mounts, and inject the actual iframe only when the user clicks \"Start.\" This matches Lighthouse's recommendation for deferring third-party resources.</p>\n\n<p>There's a Next.js static wiki case in the DEV community that uses the facade pattern to move a YouTube iframe from hydration-time mount to click-time load, bringing mobile TBT down from destructive levels to acceptable.</p>\n\n<p><strong>This means: the framework choice's impact on first-load performance is far smaller than the iframe initialization strategy's impact.</strong> I wrote this down because it directly changed my selection criteria.</p>\n\n\n\n\n<h2>\n  \n  \n  Final Choice: Hybrid Architecture\n</h2>\n\n<p>Based on the three-day comparison, I made these decisions:</p>\n\n<p><strong>Static content pages (homepage, category pages, detail pages) use Next.js SSG.</strong> Because:</p>\n\n<ul>\n<li>SEO works out of the box, no extra plugins</li>\n<li>Dynamic routes have limitations, but the game detail page ID count is manageable (only 6 games initially)</li>\n<li>If the game count grows to 200+, a Headless CMS with ISR can be introduced, but that requires a server and breaks the zero-backend constraint</li>\n</ul>\n\n<p><strong>The game runtime page uses an independent client-rendered shell.</strong> Because:</p>\n\n<ul>\n<li>The game runtime page has low SEO value and doesn't need prerendering</li>\n<li>Use <code>next/dynamic</code> with <code>ssr: false</code> to lazy-load the game iframe component</li>\n<li>Facade pattern: initialize the iframe only after the user clicks \"Start\"</li>\n</ul>\n\n<p><strong>The Vue version wasn't maintained.</strong> It builds faster and has a smaller bundle, but SEO needs extra plugins, and if the game count grows, the prerendering solution's scalability is worse than Next.js's SSG system.</p>\n\n<p>Here's the concrete implementation:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// components/GameLauncher.tsx</span>\n<span class=\"dl\">'</span><span class=\"s1\">use client</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">useState</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">react</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"nx\">dynamic</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">next/dynamic</span><span class=\"dl\">'</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">GameIframe</span> <span class=\"o\">=</span> <span class=\"nf\">dynamic</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"k\">import</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">./GameIframe</span><span class=\"dl\">'</span><span class=\"p\">),</span> <span class=\"p\">{</span>\n  <span class=\"na\">ssr</span><span class=\"p\">:</span> <span class=\"kc\">false</span><span class=\"p\">,</span>\n  <span class=\"na\">loading</span><span class=\"p\">:</span> <span class=\"p\">()</span> <span class=\"o\">=&gt;</span> <span class=\"o\">&lt;</span><span class=\"nx\">div</span> <span class=\"nx\">className</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">h-full bg-zinc-900 animate-pulse</span><span class=\"dl\">\"</span> <span class=\"o\">/&gt;</span>\n<span class=\"p\">})</span>\n\n<span class=\"k\">export</span> <span class=\"k\">default</span> <span class=\"kd\">function</span> <span class=\"nf\">GameLauncher</span><span class=\"p\">({</span> <span class=\"nx\">game</span> <span class=\"p\">})</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"p\">[</span><span class=\"nx\">started</span><span class=\"p\">,</span> <span class=\"nx\">setStarted</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"nf\">useState</span><span class=\"p\">(</span><span class=\"kc\">false</span><span class=\"p\">)</span>\n\n  <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">started</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"k\">return </span><span class=\"p\">(</span>\n      <span class=\"o\">&lt;</span><span class=\"nx\">button</span>\n        <span class=\"nx\">onClick</span><span class=\"o\">=</span><span class=\"p\">{()</span> <span class=\"o\">=&gt;</span> <span class=\"nf\">setStarted</span><span class=\"p\">(</span><span class=\"kc\">true</span><span class=\"p\">)}</span>\n        <span class=\"nx\">className</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">relative w-full aspect-video rounded-lg overflow-hidden</span><span class=\"dl\">\"</span>\n      <span class=\"o\">&gt;</span>\n        <span class=\"o\">&lt;</span><span class=\"nx\">img</span> <span class=\"nx\">src</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"nx\">game</span><span class=\"p\">.</span><span class=\"nx\">thumbnail</span><span class=\"p\">}</span> <span class=\"nx\">alt</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"nx\">game</span><span class=\"p\">.</span><span class=\"nx\">title</span><span class=\"p\">}</span> <span class=\"nx\">className</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">w-full h-full object-cover</span><span class=\"dl\">\"</span> <span class=\"o\">/&gt;</span>\n        <span class=\"o\">&lt;</span><span class=\"nx\">div</span> <span class=\"nx\">className</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">absolute inset-0 flex items-center justify-center</span><span class=\"dl\">\"</span><span class=\"o\">&gt;</span>\n          <span class=\"o\">&lt;</span><span class=\"nx\">svg</span> <span class=\"nx\">width</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">64</span><span class=\"dl\">\"</span> <span class=\"nx\">height</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">64</span><span class=\"dl\">\"</span> <span class=\"nx\">viewBox</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">0 0 64 64</span><span class=\"dl\">\"</span><span class=\"o\">&gt;</span>\n            <span class=\"o\">&lt;</span><span class=\"nx\">circle</span> <span class=\"nx\">cx</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">32</span><span class=\"dl\">\"</span> <span class=\"nx\">cy</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">32</span><span class=\"dl\">\"</span> <span class=\"nx\">r</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">30</span><span class=\"dl\">\"</span> <span class=\"nx\">fill</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">rgba(0,0,0,0.6)</span><span class=\"dl\">\"</span> <span class=\"o\">/&gt;</span>\n            <span class=\"o\">&lt;</span><span class=\"nx\">polygon</span> <span class=\"nx\">points</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">26,20 26,44 46,32</span><span class=\"dl\">\"</span> <span class=\"nx\">fill</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">white</span><span class=\"dl\">\"</span> <span class=\"o\">/&gt;</span>\n          <span class=\"o\">&lt;</span><span class=\"sr\">/svg</span><span class=\"err\">&gt;\n</span>        <span class=\"o\">&lt;</span><span class=\"sr\">/div</span><span class=\"err\">&gt;\n</span>      <span class=\"o\">&lt;</span><span class=\"sr\">/button</span><span class=\"err\">&gt;\n</span>    <span class=\"p\">)</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">return</span> <span class=\"o\">&lt;</span><span class=\"nx\">GameIframe</span> <span class=\"nx\">src</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"nx\">game</span><span class=\"p\">.</span><span class=\"nx\">entry</span><span class=\"p\">}</span> <span class=\"nx\">gameId</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"nx\">game</span><span class=\"p\">.</span><span class=\"nx\">id</span><span class=\"p\">}</span> <span class=\"sr\">/</span><span class=\"err\">&gt;\n</span><span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><code>ssr: false</code> ensures the iframe component isn't bundled into the HTML during server rendering, and only loads from the client after the user clicks. This matches Next.js's official lazy-loading advice: for heavy components that don't participate in SSR, use <code>next/dynamic</code> with <code>ssr: false</code> to reduce the initial bundle.</p>\n\n\n\n\n<h2>\n  \n  \n  Data Comparison\n</h2>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Next.js 14</th>\n<th>Vue 3 + Vite</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>First-load JS (gzip)</td>\n<td>~85KB</td>\n<td>~50KB</td>\n</tr>\n<tr>\n<td>Homepage LCP (4G)</td>\n<td>1.2s</td>\n<td>1.5s (after prerendering)</td>\n</tr>\n<tr>\n<td>Build time</td>\n<td>12s</td>\n<td>6s</td>\n</tr>\n<tr>\n<td>Dynamic route support</td>\n<td>Needs generateStaticParams</td>\n<td>Built-in</td>\n</tr>\n<tr>\n<td>SEO</td>\n<td>Out of the box</td>\n<td>Needs extra plugins</td>\n</tr>\n<tr>\n<td>Game runtime lazy load</td>\n<td>next/dynamic native support</td>\n<td>Manual implementation</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>Test conditions: MacBook Pro M1 build, iPhone 12 + Chrome DevTools 4G throttling, 10 runs each, median.</p>\n\n<p>Note: Vue's LCP was actually slower than Next.js after prerendering. The reason is that <code>vite-plugin-seo-prerender</code> still includes the full Vue runtime in the generated HTML, while Next.js SSG outputs cleaner HTML with lower hydration cost.</p>\n\n\n\n\n<h2>\n  \n  \n  Trade-offs\n</h2>\n\n<ul>\n<li>\n<strong>Next.js build time is longer</strong>: 12s vs 6s. Every new game requires a rebuild. If the game count reaches 50, build time could exceed 30 seconds.</li>\n<li>\n<strong>The Vue version was abandoned</strong>: It has better dev experience and a smaller bundle, but needs extra work on SEO and scalability. If your game site doesn't need SEO — an internal tool or pure entertainment site — Vue + Vite is the better choice.</li>\n<li>\n<strong>The hybrid architecture adds complexity</strong>: Static pages and the runtime page use different rendering strategies, requiring understanding of two sets of logic.</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Unresolved\n</h2>\n\n<ul>\n<li>If the game count exceeds 100, <code>generateStaticParams</code> full pre-generation becomes a bottleneck, requiring ISR or a Headless CMS — but that's no longer zero-backend.</li>\n<li>I didn't dig deep into Vue's prerendering solution. <code>vite-plugin-prerender-static</code> supports multi-route generation and SEO meta tags, which might be enough if the game count is small.</li>\n<li>Astro is another direction worth watching. A developer built a 50+ game site with Astro + vanilla JS, keeping the JS bundle under 100KB gzip and emphasizing \"each game only loads the JavaScript it actually needs.\"</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Repo\n</h2>\n\n<p>Both MVP versions are organized, including Next.js and Vue comparison branches. The test data comes from a local environment. If you get different results on real hardware, I'd love to hear about it.</p>","score":3},{"source":"https://dev.to/feed/tag/nuxt","sourceHost":"dev.to","title":"Nuxt Hydration Mismatch: Why It Happens and How to Fix It","link":"https://dev.to/parsajiravand/nuxt-hydration-mismatch-why-it-happens-and-how-to-fix-it-5b7i","pubDate":"Sun, 20 Sep 2026 16:26:04 +0000","description":"<p>Your Nuxt page looks perfect. \"View Source\" shows clean, fully-rendered HTML — the hero text, the product price, the footer, all there before a single line of JavaScript ran. Then the client bundle finishes loading, and the console lights up: <code>[Vue warn]: Hydration text mismatch</code>. Sometimes it's cosmetic — a number flickers and settles. Sometimes it's worse: a button the user already clicked stops responding, because Vue just tore out the DOM node it was attached to and built a new one.</p>\n\n<p>This is a hydration mismatch, and it's arguably the most <em>Nuxt-specific</em> bug you'll ever debug. It has nothing to do with your logic being wrong in the way a typo is wrong — your component can be perfectly correct JavaScript and still cause one, because the bug isn't in what you wrote, it's in the fact that Nuxt runs what you wrote <strong>twice, in two different places</strong>, and bets your app's interactivity on both runs agreeing.</p>\n\n<p>This article is written against <strong>Nuxt 4.x</strong> (verified against the v4.5 release line, August 2026), using the Composition API, auto-imports, and the <code>app/</code> directory convention Nuxt 4 defaults to. Everything here also applies to Nuxt 3's <code>compatibilityVersion: 4</code> mode.</p>\n\n<h2>\n  \n  \n  What you'll learn\n</h2>\n\n<p>By the end of this article you'll be able to:</p>\n\n<ul>\n<li>Explain exactly what \"hydration\" means in Nuxt and why a mismatch happens</li>\n<li>Recognize the handful of code patterns that reliably cause one</li>\n<li>Pick the right fix — <code>onMounted</code>, <code>&lt;ClientOnly&gt;</code>, or <code>data-allow-mismatch</code> — for each situation</li>\n<li>Read a hydration warning and know which line of your code to blame</li>\n<li>Avoid the \"fix\" that looks reasonable but guarantees a mismatch every time</li>\n</ul>\n\n<h2>\n  \n  \n  Who this is for\n</h2>\n\n<p>You've built at least one Nuxt page with <code>&lt;script setup&gt;</code> and know roughly what server-side rendering means (the server sends back real HTML instead of an empty <code>&lt;div id=\"app\"&gt;</code>). You don't need prior SSR debugging experience — that's the point of this article.</p>\n\n<h2>\n  \n  \n  Table of contents\n</h2>\n\n<ul>\n<li>The problem: a page that's \"correct\" and still breaks</li>\n<li>The mental model: two renders, one DOM</li>\n<li>Fixing it, stage by stage</li>\n<li>Edge cases and gotchas</li>\n<li>Best practices</li>\n<li>FAQ</li>\n<li>Cheat sheet</li>\n<li>Key takeaways</li>\n</ul>\n\n<h2>\n  \n  \n  The problem: a page that's \"correct\" and still breaks\n</h2>\n\n<p>Say you're building a \"tip of the day\" widget. It's a plain computed value, no fetch, no state management — about as simple as a Vue component gets:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight vue\"><code><span class=\"nt\">&lt;</span><span class=\"k\">script</span> <span class=\"na\">setup</span><span class=\"nt\">&gt;</span>\n<span class=\"kd\">const</span> <span class=\"nx\">TIPS</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n  <span class=\"dl\">\"</span><span class=\"s2\">Use useAsyncData for anything that fetches.</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n  <span class=\"dl\">\"</span><span class=\"s2\">Auto-imports save you the import line, not the thinking.</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n  <span class=\"dl\">\"</span><span class=\"s2\">Nitro is just Node under the hood.</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">tip</span> <span class=\"o\">=</span> <span class=\"nx\">TIPS</span><span class=\"p\">[</span><span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">floor</span><span class=\"p\">(</span><span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">random</span><span class=\"p\">()</span> <span class=\"o\">*</span> <span class=\"nx\">TIPS</span><span class=\"p\">.</span><span class=\"nx\">length</span><span class=\"p\">)]</span>\n<span class=\"nt\">&lt;/</span><span class=\"k\">script</span><span class=\"nt\">&gt;</span>\n\n<span class=\"nt\">&lt;</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;p&gt;</span>Tip of the day: <span class=\"si\">{{</span> <span class=\"nx\">tip</span> <span class=\"si\">}}</span><span class=\"nt\">&lt;/p&gt;</span>\n<span class=\"nt\">&lt;/</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Nothing here looks wrong. It compiles, it runs, <code>npm run dev</code> shows a tip. But open the browser console and you'll see something like:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>[Vue warn]: Hydration text mismatch:\n- Server rendered:  Tip of the day: Nitro is just Node under the hood.\n- Client rendered:  Tip of the day: Use useAsyncData for anything that fetches.\n</code></pre>\n\n</div>\n\n\n\n<p>Nothing crashed. The page still works. But the text the user saw for a split second — the one baked into the HTML the server sent — silently got replaced by a different one the instant the JavaScript took over. If that \"tip\" were a price, a username, or which item was in stock, this wouldn't be a curiosity, it would be a bug report.</p>\n\n<p>The same failure mode shows up with <code>new Date()</code>, with <code>window.innerWidth</code>, with anything read from <code>localStorage</code> inside the component's render path. The common thread: the value depends on <em>where</em> the code runs, and Nuxt runs your component in two different places.</p>\n\n<h2>\n  \n  \n  The mental model: two renders, one DOM\n</h2>\n\n<p><strong>The mental model:</strong> Nuxt doesn't render your app once — it renders the same component tree twice, in two different environments, and then asks the second render to <em>adopt</em> the DOM the first render already produced, instead of rebuilding it from scratch.</p>\n\n<p>Here's the sequence for a single page request:</p>\n\n<ol>\n<li>A request hits your server. Nitro runs your Vue app in Node — no browser, no DOM — and walks your components to produce a plain HTML string, plus a serialized <strong>payload</strong>: the results of every <code>useAsyncData</code>/<code>useFetch</code> call and every <code>useState</code>, embedded in the page as a <code>&lt;script id=\"__NUXT_DATA__\"&gt;</code> block.</li>\n<li>The browser receives that HTML and paints it immediately. This is the entire point of SSR — the user sees real content before a single byte of your JavaScript bundle has downloaded.</li>\n<li>The client bundle downloads and boots the <em>same</em> Vue app, client-side. But instead of creating new DOM nodes the way a client-only SPA would, it runs in <strong>hydration mode</strong>: it walks the existing DOM the server produced, node by node, and attaches reactivity and event listeners to what's already there, reading the payload from step 1 so it doesn't have to re-fetch data the server already fetched.</li>\n</ol>\n\n<p>Hydration is a <em>reconciliation</em>, not a second render from scratch — and reconciliation assumes the two renders agree. When they do, hydration is invisible: the DOM stays exactly as the server drew it, listeners attach, the page becomes interactive. When they don't, Vue has two options depending on how badly they disagree:</p>\n\n<ul>\n<li>\n<strong>A text or attribute mismatch</strong> (a <code>{{ tip }}</code> that resolved differently, a class that differs): Vue patches just that value in place and — in development only — logs a warning. Production builds do this silently, which is why a mismatch can ship for weeks before anyone notices.</li>\n<li>\n<strong>A structural mismatch</strong> (a different tag, a different number of children — the kind you get from <code>v-if</code> branching differently on each side): Vue can't patch that in place. It throws away the mismatched subtree and re-renders it entirely client-side. That's real, visible re-work, and if a user had already interacted with something inside that subtree, the element they clicked no longer exists.</li>\n</ul>\n\n<p>The payload exists specifically so that data <em>is</em> safe across hydration — <code>useAsyncData</code>, <code>useFetch</code>, and <code>useState</code> all serialize their results, so the client reads the exact value the server used instead of recomputing it. (If you've read the <a href=\"https://dev.to/parsajiravand/useasyncdata-keys-in-nuxt-caching-dedupe-the-sharing-bug-el1\">earlier episode on <code>useAsyncData</code> keys and dedupe</a>, this is the same payload that makes dedupe possible — it's doing double duty.) The danger is everything <em>outside</em> that mechanism: any value your template reads that isn't backed by <code>useState</code>/<code>useAsyncData</code> and isn't guaranteed identical on both sides — <code>Math.random()</code>, <code>Date.now()</code>, <code>window</code>, <code>navigator</code>, <code>localStorage</code> — is a mismatch waiting to happen, because nothing carries it across the server→client boundary for you.</p>\n\n<h2>\n  \n  \n  Fixing it, stage by stage\n</h2>\n\n<h3>\n  \n  \n  Stage 1: defer the value with <code>onMounted</code>\n</h3>\n\n<p>The tip-of-the-day bug and the \"current time\" bug are the same shape: a value that's <em>legitimately</em> allowed to differ per visitor, rendered directly during setup. The fix is to give the template a stable, server-safe default, and only fill in the real value once you're certain you're client-side:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight vue\"><code><span class=\"nt\">&lt;</span><span class=\"k\">script</span> <span class=\"na\">setup</span><span class=\"nt\">&gt;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">ref</span><span class=\"p\">,</span> <span class=\"nx\">onMounted</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">\"</span><span class=\"s2\">vue</span><span class=\"dl\">\"</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">tip</span> <span class=\"o\">=</span> <span class=\"nf\">ref</span><span class=\"p\">(</span><span class=\"kc\">null</span><span class=\"p\">)</span>\n\n<span class=\"nf\">onMounted</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">TIPS</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"dl\">\"</span><span class=\"s2\">Use useAsyncData for anything that fetches.</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"dl\">\"</span><span class=\"s2\">…</span><span class=\"dl\">\"</span><span class=\"p\">]</span>\n  <span class=\"nx\">tip</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"o\">=</span> <span class=\"nx\">TIPS</span><span class=\"p\">[</span><span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">floor</span><span class=\"p\">(</span><span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">random</span><span class=\"p\">()</span> <span class=\"o\">*</span> <span class=\"nx\">TIPS</span><span class=\"p\">.</span><span class=\"nx\">length</span><span class=\"p\">)]</span>\n<span class=\"p\">})</span>\n<span class=\"nt\">&lt;/</span><span class=\"k\">script</span><span class=\"nt\">&gt;</span>\n\n<span class=\"nt\">&lt;</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;p&gt;</span>Tip of the day: <span class=\"si\">{{</span> <span class=\"nx\">tip</span> <span class=\"o\">??</span> <span class=\"dl\">\"</span><span class=\"s2\">Loading…</span><span class=\"dl\">\"</span> <span class=\"si\">}}</span><span class=\"nt\">&lt;/p&gt;</span>\n<span class=\"nt\">&lt;/</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>Key concept:</strong> <code>onMounted</code> runs only after hydration has already completed successfully. Anything it writes is a normal, client-only reactive update — Vue never has to reconcile it against server HTML, because by the time it runs, hydration is already done.</p>\n\n<h3>\n  \n  \n  Stage 2: skip SSR entirely with <code>&lt;ClientOnly&gt;</code>\n</h3>\n\n<p>Some content isn't \"slightly different\" between server and client — it can't exist on the server at all. A chart that measures its container's pixel width, a widget that reads <code>localStorage</code>, a third-party embed that expects <code>window</code>. For those, don't try to make the server render <em>something</em> — tell Nuxt not to render it there in the first place. <code>&lt;ClientOnly&gt;</code> is auto-imported and does exactly that:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight vue\"><code><span class=\"nt\">&lt;</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;ClientOnly&gt;</span>\n    <span class=\"nt\">&lt;UserLocalClock</span> <span class=\"nt\">/&gt;</span>\n    <span class=\"nt\">&lt;template</span> <span class=\"na\">#fallback</span><span class=\"nt\">&gt;</span>\n      <span class=\"nt\">&lt;span</span> <span class=\"na\">class=</span><span class=\"s\">\"clock-placeholder\"</span><span class=\"nt\">&gt;</span>--:--<span class=\"nt\">&lt;/span&gt;</span>\n    <span class=\"nt\">&lt;/</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;/ClientOnly&gt;</span>\n<span class=\"nt\">&lt;/template&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The default slot never runs on the server. The <code>#fallback</code> slot renders there instead (useful for reserving layout space so nothing jumps), and the moment the component mounts client-side, Nuxt swaps the fallback for the real content — created fresh, never hydrated.</p>\n\n<p><strong>Key concept:</strong> <code>&lt;ClientOnly&gt;</code> doesn't resolve a mismatch — it removes the possibility of one, because nothing inside it is ever compared between two renders. There's only ever one render, on the client.</p>\n\n<h3>\n  \n  \n  Stage 3: the branch that looks like a fix but isn't\n</h3>\n\n<p>It's tempting to reach for Nuxt's environment flags — <code>import.meta.server</code> / <code>import.meta.client</code> (the modern replacement for the older <code>process.server</code> / <code>process.client</code>) — and branch your template directly on them:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight vue\"><code><span class=\"c\">&lt;!-- Don't do this --&gt;</span>\n<span class=\"nt\">&lt;</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;div</span> <span class=\"na\">v-if=</span><span class=\"s\">\"import.meta.client\"</span><span class=\"nt\">&gt;</span>Client-rendered content<span class=\"nt\">&lt;/div&gt;</span>\n  <span class=\"nt\">&lt;div</span> <span class=\"na\">v-else</span><span class=\"nt\">&gt;</span>Server-rendered content<span class=\"nt\">&lt;/div&gt;</span>\n<span class=\"nt\">&lt;/</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<p>This guarantees a structural mismatch, every single time. On the server, <code>import.meta.server</code> is <code>true</code>, so the server emits the <code>&lt;div&gt;</code> from the <code>v-else</code> branch. On the client, during hydration, <code>import.meta.client</code> is <code>true</code>, so Vue's hydration walk expects the <code>v-if</code> branch — a different <code>&lt;div&gt;</code> than the one actually sitting in the DOM. Vue can't reconcile two different branches in place; it discards and re-renders. <code>import.meta.client</code>/<code>.server</code> are genuinely useful for deciding <em>what code runs</em> (skip a browser-only import on the server, skip a Node-only one on the client) — they're the wrong tool for deciding what a hydrated template <em>renders</em>, because that decision has to be identical in both places by definition.</p>\n\n<h3>\n  \n  \n  Stage 4: when a mismatch is real, expected, and fine — <code>data-allow-mismatch</code>\n</h3>\n\n<p>Occasionally you'll have a value that will <em>always</em> differ by design — a relative timestamp (\"posted 3 minutes ago\") that keeps ticking, for instance — and you've already accepted that as correct behavior rather than a bug. Vue 3.5 added an attribute for exactly this: <code>data-allow-mismatch</code> silences the hydration warning for a specific element, scoped to the kind of mismatch you name (<code>text</code>, <code>children</code>, <code>class</code>, <code>style</code>, or <code>attribute</code>):<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight vue\"><code><span class=\"nt\">&lt;time</span> <span class=\"na\">data-allow-mismatch=</span><span class=\"s\">\"text\"</span><span class=\"nt\">&gt;</span>{{ relativeTime }}<span class=\"nt\">&lt;/time&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<p>This only suppresses the console warning — it does nothing to make the values agree. Reach for it after you've decided the mismatch is cosmetic and harmless, never as a first response to a warning you haven't diagnosed yet.</p>\n\n<h2>\n  \n  \n  Edge cases and gotchas\n</h2>\n\n<ul>\n<li>\n<strong>Invalid HTML nesting causes mismatches with no logic bug at all.</strong> A <code>&lt;div&gt;</code> nested inside a <code>&lt;p&gt;</code>, or malformed <code>&lt;table&gt;</code> markup, gets silently corrected by the browser's HTML parser while it parses the server's HTML — the browser closes the <code>&lt;p&gt;</code> early, restructuring the tree Vue expected to hydrate onto. The fix is markup hygiene, not JavaScript: keep nesting valid per the HTML content model.</li>\n<li>\n<strong>Browser extensions mutate the DOM before your JS runs.</strong> Grammarly, password managers, and dark-mode extensions routinely inject attributes into the page before hydration starts. These aren't your bug and can't be reliably prevented; <code>data-allow-mismatch=\"attribute\"</code> on the affected element is the pragmatic escape valve once you've confirmed the source.</li>\n<li>\n<strong>Server and client timezones differ.</strong> A server running in UTC formatting a date directly in a template will disagree with a client in the visitor's local timezone. Same class of bug as <code>Date.now()</code> — same fix: compute the display string in <code>onMounted</code>.</li>\n<li>\n<strong>A <code>ref</code> seeded from a browser API at module or setup scope.</strong> <code>const isWide = ref(window.innerWidth &gt; 768)</code> throws on the server (there is no <code>window</code>) or, if guarded, still needs a server-safe default and a client-side correction — the same <code>onMounted</code> pattern applies.</li>\n<li>\n<strong>Shared server state is a related but different bug.</strong> If your mismatch is about the <em>wrong user's</em> data appearing rather than a timing difference, that's the cross-request state leak, not a hydration mismatch — see the <a href=\"https://dev.to/parsajiravand/nuxt-usestate-vs-ref-why-server-state-leaks-across-users-47n1\">earlier episode on <code>useState</code> vs. a plain <code>ref</code></a> if that's the symptom you're chasing.</li>\n</ul>\n\n<h2>\n  \n  \n  Best practices\n</h2>\n\n<ul>\n<li>\n<strong>Ask one question of every render-affecting expression:</strong> given the same props and payload, does this produce the exact same output on the server and the client? If the honest answer is \"no,\" it doesn't belong directly in the template.</li>\n<li>\n<strong>Default first, correct in <code>onMounted</code>.</strong> Any value that's allowed to differ per visitor gets a server-safe placeholder and a client-side update after mount — never a direct read of a browser API during setup.</li>\n<li>\n<strong>Reach for <code>&lt;ClientOnly&gt;</code> for whole widgets, not individual values.</strong> If an entire component only makes sense in a browser (canvas-sized charts, <code>window</code>-dependent libraries), don't fight it into an SSR-safe shape — skip SSR for it.</li>\n<li>\n<strong>Never branch a hydrated template's markup on <code>import.meta.client</code>/<code>.server</code>.</strong> Use those flags to decide what code <em>runs</em>, not what a hydrated component <em>renders</em>.</li>\n<li>\n<strong>Lint your markup.</strong> Invalid HTML nesting is an easy, boring source of mismatches that a markup or accessibility linter catches before it ever reaches a browser.</li>\n<li>\n<strong>Test against a production build, not just <code>nuxt dev</code>.</strong> Run <code>nuxt build &amp;&amp; nuxt preview</code> before shipping something that touches SSR — dev's warnings are the same, but dev's timing can mask issues that show up under real hydration.</li>\n</ul>\n\n<h2>\n  \n  \n  FAQ\n</h2>\n\n<h3>\n  \n  \n  Does a hydration mismatch crash my app?\n</h3>\n\n<p>No — Vue reconciles it either way. A text/attribute mismatch is patched in place; a structural one is discarded and re-rendered client-side. The app keeps working, but a structural mismatch means real extra work and a possible flash or loss of state in that subtree.</p>\n\n<h3>\n  \n  \n  Why does the warning only appear in development?\n</h3>\n\n<p>Vue's hydration mismatch console warning is a development-only diagnostic. In a production build, the same reconciliation happens, but silently — which is exactly why these bugs can ship unnoticed for a long time. Always sanity-check SSR-sensitive pages against a <code>nuxt preview</code> build, not just dev.</p>\n\n<h3>\n  \n  \n  Is <code>&lt;ClientOnly&gt;</code> the same thing as checking <code>import.meta.client</code>?\n</h3>\n\n<p>No. <code>import.meta.client</code> is a compile-time flag that decides which lines of code are included in which bundle — it's a build-time tool. <code>&lt;ClientOnly&gt;</code> is a runtime component that skips server rendering for its slot content and mounts it fresh in the browser. Using the flag to branch a hydrated template's markup causes the exact mismatch this article is about; <code>&lt;ClientOnly&gt;</code> avoids it by never hydrating that content at all.</p>\n\n<h3>\n  \n  \n  Does <code>useState</code> prevent hydration mismatches?\n</h3>\n\n<p>It prevents the specific class caused by state disagreeing between server and client, because its value is serialized into the payload and read identically on both sides. It doesn't protect a value your template computes independently of <code>useState</code> — <code>Math.random()</code> inside a <code>&lt;script setup&gt;</code> block is still a mismatch even if an unrelated <code>useState</code> call exists elsewhere in the same component.</p>\n\n<h3>\n  \n  \n  Can a mismatch happen even when my code is completely correct?\n</h3>\n\n<p>Yes. Third-party scripts and browser extensions can alter the DOM before your app hydrates, and that's outside your code's control. <code>data-allow-mismatch</code> on the specific affected attribute is the accepted mitigation once you've confirmed that's the cause.</p>\n\n<h2>\n  \n  \n  Cheat sheet\n</h2>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Situation</th>\n<th>Symptom</th>\n<th>Fix</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>\n<code>Math.random()</code> / <code>Date.now()</code> read during setup or render</td>\n<td>Text mismatch warning, value flickers on load</td>\n<td>Default to <code>null</code>/placeholder, set the real value in <code>onMounted</code>\n</td>\n</tr>\n<tr>\n<td>Reading <code>window</code>, <code>navigator</code>, <code>localStorage</code> in the template's data path</td>\n<td>Throws on server, or mismatches if guarded naively</td>\n<td>\n<code>ref(defaultValue)</code> + <code>onMounted</code> to correct it</td>\n</tr>\n<tr>\n<td>Whole widget only makes sense client-side (canvas size, browser-only lib)</td>\n<td>Mismatch or server crash</td>\n<td>Wrap it in <code>&lt;ClientOnly&gt;</code> with a <code>#fallback</code>\n</td>\n</tr>\n<tr>\n<td>\n<code>v-if=\"import.meta.client\"</code> branching a hydrated template</td>\n<td>Structural mismatch, guaranteed, every load</td>\n<td>Don't branch markup on the flag — use <code>&lt;ClientOnly&gt;</code>/<code>onMounted</code> instead</td>\n</tr>\n<tr>\n<td>Relative time / genuinely-expected drift you've accepted</td>\n<td>Warning you don't want to see</td>\n<td>\n<code>data-allow-mismatch=\"text\"</code> (Vue 3.5+) — after you've confirmed it's harmless</td>\n</tr>\n<tr>\n<td>\n<code>&lt;div&gt;</code> inside <code>&lt;p&gt;</code>, broken table markup</td>\n<td>Mismatch with no obvious cause in your JS</td>\n<td>Fix the HTML nesting; lint markup</td>\n</tr>\n<tr>\n<td>Grammarly / extensions injecting attributes</td>\n<td>Attribute mismatch you can't reproduce locally without the extension</td>\n<td>\n<code>data-allow-mismatch=\"attribute\"</code> on the affected element</td>\n</tr>\n</tbody>\n</table></div>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight vue\"><code><span class=\"nt\">&lt;</span><span class=\"k\">script</span> <span class=\"na\">setup</span><span class=\"nt\">&gt;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">ref</span><span class=\"p\">,</span> <span class=\"nx\">onMounted</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">\"</span><span class=\"s2\">vue</span><span class=\"dl\">\"</span>\n\n<span class=\"c1\">// Server-safe default — identical on both renders.</span>\n<span class=\"kd\">const</span> <span class=\"nx\">clientValue</span> <span class=\"o\">=</span> <span class=\"nf\">ref</span><span class=\"p\">(</span><span class=\"kc\">null</span><span class=\"p\">)</span>\n\n<span class=\"nf\">onMounted</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"c1\">// Runs only after hydration succeeds — safe to diverge here.</span>\n  <span class=\"nx\">clientValue</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"o\">=</span> <span class=\"nf\">computeSomethingClientOnly</span><span class=\"p\">()</span>\n<span class=\"p\">})</span>\n<span class=\"nt\">&lt;/</span><span class=\"k\">script</span><span class=\"nt\">&gt;</span>\n\n<span class=\"nt\">&lt;</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;p&gt;</span><span class=\"si\">{{</span> <span class=\"nx\">clientValue</span> <span class=\"o\">??</span> <span class=\"dl\">\"</span><span class=\"s2\">Loading…</span><span class=\"dl\">\"</span> <span class=\"si\">}}</span><span class=\"nt\">&lt;/p&gt;</span>\n\n  <span class=\"c\">&lt;!-- For whole subtrees that can never run on the server: --&gt;</span>\n  <span class=\"nt\">&lt;ClientOnly&gt;</span>\n    <span class=\"nt\">&lt;BrowserOnlyWidget</span> <span class=\"nt\">/&gt;</span>\n    <span class=\"nt\">&lt;template</span> <span class=\"na\">#fallback</span><span class=\"nt\">&gt;&lt;span&gt;</span>Loading…<span class=\"nt\">&lt;/span&gt;&lt;/</span><span class=\"k\">template</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;/ClientOnly&gt;</span>\n<span class=\"nt\">&lt;/template&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<h2>\n  \n  \n  🎮 Try it yourself\n</h2>\n\n<p><strong><a href=\"https://bestpractic.org/blog/nuxt-weekly-hydration-mismatch/playground\" rel=\"noopener noreferrer\">▶️ Open the interactive playground →</a></strong></p>\n\n<p><em>Runs right in your browser — poke at it and watch the concept react live.</em></p>\n\n<h2>\n  \n  \n  Key takeaways\n</h2>\n\n<ul>\n<li>A hydration mismatch happens because Nuxt renders your app twice — once on the server, once in the browser — and hydration assumes, without verifying up front, that both renders agree.</li>\n<li>The near-universal cause is a render-affecting value that isn't guaranteed identical on both sides: <code>Math.random()</code>, <code>Date.now()</code>, or any direct read of a browser-only API.</li>\n<li>\n<code>onMounted</code> fixes values that are allowed to differ once hydration is already done; <code>&lt;ClientOnly&gt;</code> fixes whole subtrees that can never run on the server; <code>data-allow-mismatch</code> only silences a warning you've already confirmed is harmless.</li>\n<li>Never branch a hydrated template's markup on <code>import.meta.client</code>/<code>.server</code> — that's the one \"fix\" that reliably causes the exact bug it's trying to solve.</li>\n</ul>\n\n<h2>\n  \n  \n  🧠 Test yourself\n</h2>\n\n<p>Think it clicked? <strong><a href=\"https://bestpractic.org/blog/nuxt-weekly-hydration-mismatch/quiz\" rel=\"noopener noreferrer\">Take the 9-question quiz →</a></strong></p>\n\n<p><em>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.</em></p>\n\n<h2>\n  \n  \n  One more render to get right\n</h2>\n\n<p>That tip-of-the-day widget from the top of this article has an honest fix now — a <code>ref</code> that starts <code>null</code> and fills in after mount, instead of a <code>Math.random()</code> call sitting directly in the render path. The bug was never really about randomness; it was about <em>where</em> the randomness ran, and Nuxt was always going to run it twice.</p>\n\n<p>What's the strangest hydration mismatch you've had to track down — a third-party script, a timezone, something stranger? Drop it in the comments; there's a decent chance someone else's next <code>[Vue warn]</code> is exactly the one you already solved.</p>\n\n\n\n\n<p>🚀 <strong>Want more like this?</strong> Every guide, playground, and quiz lives on <strong><a href=\"https://bestpractic.org/\" rel=\"noopener noreferrer\">bestpractic.org</a></strong> — open it and <strong><a href=\"https://bestpractic.org/\" rel=\"noopener noreferrer\">sign up free</a></strong> so the next one finds you.</p>\n\n<p><em>Thanks for reading! Let's stay connected:</em></p>\n\n<ul>\n<li>⭐ <strong>GitHub</strong> — follow me and star the projects: <a href=\"https://github.com/parsajiravand\" rel=\"noopener noreferrer\">github.com/parsajiravand</a>\n</li>\n<li>💬 <strong>Discord</strong> — join the frontend best-practices community: <a href=\"https://discord.gg/d9KRhuAwQ\" rel=\"noopener noreferrer\">discord.gg/d9KRhuAwQ</a>\n</li>\n<li>📸 <strong>Instagram</strong> — frontend best practices, daily: <a href=\"https://www.instagram.com/bestpractice___/\" rel=\"noopener noreferrer\">@bestpractice___</a>\n</li>\n</ul>","score":3},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"Building a Real-Time Football Live Score & Standings App with Nuxt 3","link":"https://dev.to/cocatips/building-a-real-time-football-live-score-standings-app-with-nuxt-3-3249","pubDate":"Sun, 20 Sep 2026 10:45:39 +0000","description":"<p>Building a high-concurrency sports data platform is no easy task. When we started developing <a href=\"https://cocatips.com\" rel=\"noopener noreferrer\">Cocatips - Live Football Scores &amp; AI Predictions</a>, our goal was to process data for over 80,000 football clubs globally without crashing the server or sacrificing UX.</p>\n\n<p>In this post, I will share the architectural approach we took using <strong>Nuxt 3</strong> and <strong>Vue.js</strong> to handle massive data structures, specifically for live sports events, standings, and algorithmic predictions.</p>\n\n<h2>\n  \n  \n  1. The Challenge of Real-Time Sports Data\n</h2>\n\n<p>Football fans demand instant updates. Whether they are looking for <strong>live scores, fixtures &amp; schedules</strong> or diving deep into <strong>head to head (H2H) &amp; stats</strong>, the data delivery must be lightning-fast. </p>\n\n<p>Additionally, our platform processes advanced algorithmic data for sports analysts. We needed a UI that could seamlessly switch between displaying a standard match tracker and outputting deep analytical models, such as:</p>\n\n<ul>\n<li>  <strong>1x2 prediction today</strong> probabilities.</li>\n<li>  <strong>BTTS / GG predictions</strong> (Both Teams To Score).</li>\n<li>  <strong>Over 2.5 goals predictions</strong>.</li>\n</ul>\n\n<h2>\n  \n  \n  2. Dynamic Routing for League Standings\n</h2>\n\n<p>To handle SEO and dynamic rendering for thousands of leagues, we utilized Nuxt 3's Nitro engine. We set up ISR (Incremental Static Regeneration) for our highly-visited pages.</p>\n\n<p>For example, when fans check the <a href=\"https://cocatips.com/standings/english-premier-league\" rel=\"noopener noreferrer\">Live English Premier League standings, table &amp; results</a>, the page needs to show up-to-the-minute goal differences and points. By caching the initial HTML at the edge and hydrating the live data on the client side via our API (<code>datav1.cocascore.com</code>), we achieved a sub-second TTI (Time to Interactive).</p>\n\n<h2>\n  \n  \n  3. Building the UI Widget (CodePen Demo)\n</h2>\n\n<p>To demonstrate how we structure our Vue components without revealing our entire proprietary backend, I created a Vanilla JS/CSS version of our Standings Widget. </p>\n\n<p>This widget fetches real-time data and can toggle between multiple leagues dynamically. Check out the embed below:</p>\n\n<p><iframe height=\"600\" src=\"https://codepen.io/editor/cocatips/embed/01a0be36-889f-7a4c-90a1-4698bb7a6375?height=600&amp;default-tab=result&amp;embed-version=2\">\n</iframe>\n</p>\n\n<h2>\n  \n  \n  4. The AI Prediction Search Algorithm\n</h2>\n\n<p>One of the most complex parts of the system was allowing users to search through 80,000+ teams and matches instantly. We built a custom scoring algorithm in TypeScript that handles fuzzy matching and ignores diacritics.</p>\n\n<p>If a user searches for a specific matchup looking for an <strong>expert correct score prediction</strong>, our search function applies tiered sorting. It prioritizes top-tier leagues (like the Champions League or La Liga) over regional youth leagues, ensuring the most relevant matches appear first in the modal.</p>\n\n<h2>\n  \n  \n  Conclusion\n</h2>\n\n<p>By combining Nuxt 3's server-side rendering with a robust Redis-backed Node.js API, we successfully created a platform that delivers both <strong>sure home win predictions</strong> and deep statistical insights without breaking a sweat.</p>\n\n<p>If you are a Vue developer interested in sports data, feel free to check out the live architecture on our platform at <a href=\"https://cocatips.com\" rel=\"noopener noreferrer\">Cocatips.com</a> and explore our daily <a href=\"https://cocatips.com/football-tips-and-predictions-for-today\" rel=\"noopener noreferrer\">mathematical predictions</a>.</p>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Should you read the code, is RAG dead, and did Skills kill MCP?","link":"https://github.blog/ai-and-ml/should-you-read-the-code-is-rag-dead-and-did-skills-kill-mcp/","pubDate":"Fri, 18 Sep 2026 15:00:00 +0000","description":"<p>We dive into these questions and other AI hot takes on the latest episode of the GitHub Podcast.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/should-you-read-the-code-is-rag-dead-and-did-skills-kill-mcp/\">Should you read the code, is RAG dead, and did Skills kill MCP?</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://dev.to/feed/tag/nuxt","sourceHost":"dev.to","title":"I Built an AI Tarot Demo in Two Hours. Shipping the Product Was a Different Job.","link":"https://dev.to/liuxinyea/i-built-an-ai-tarot-demo-in-two-hours-shipping-the-product-was-a-different-job-23ck","pubDate":"Fri, 18 Sep 2026 09:59:28 +0000","description":"<p>My wife was interested in tarot cards, and I was a frontend developer looking for an excuse to build something. I put together a page with a deck, a card-flipping animation, and an AI-generated reading.</p>\n\n<p>With AI assistance, the basic flow worked in about two hours.</p>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5v6x68r5tomwy08k3abg.png\" class=\"article-body-image-wrapper\"><img src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5v6x68r5tomwy08k3abg.png\" alt=\" \" width=\"800\" height=\"889\"></a></p>\n\n<p>I thought authentication and payments would get it ready to launch. Then the questions started: What happens when someone switches devices? What if a paid reading stops halfway through? Can I change a guided exercise without changing the meaning of someone’s saved answers?</p>\n\n<p>Eventually, another question became harder than any of those: how do I get people to use it?</p>\n\n<p>The project is now <a href=\"https://tarot.auraflame.tech/?utm_source=devto&amp;utm_medium=article&amp;utm_campaign=demo_to_product\" rel=\"noopener noreferrer\">Mystic Journey</a>. It includes a daily card ritual, guided explorations, a personal journal, and an optional companion board.</p>\n\n<p>I’m still looking for early users. This is a development retrospective—and, openly, an invitation to try what I’ve built.</p>\n\n<h2>\n  \n  \n  Why I changed the product\n</h2>\n\n<p>The original experience was straightforward: ask a question, draw cards, read the interpretation, leave.</p>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft52sjei92pek8k4p6rqy.png\" class=\"article-body-image-wrapper\"><img src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft52sjei92pek8k4p6rqy.png\" alt=\" \" width=\"800\" height=\"808\"></a></p>\n\n<p>But a general-purpose AI can already explain tarot cards. A nicer animation wasn’t enough to explain why someone should return to my website.</p>\n\n<p>I started thinking about the experience around the reading. Writing in an empty journal can be surprisingly difficult. A card, a specific question, or a few choices might make it easier to begin reflecting.</p>\n\n<p>That became the daily ritual: choose your mood, reveal your daily card, read a reflection prompt, and optionally write a few words. The aim is to make a small check-in easy, even when you don’t have a major question to ask.</p>\n\n<p>For people who want to go further, I added six guided themes covering space, boundaries, change, confidence, connection, and direction. Each has five short chapters: notice the situation, consider another perspective, choose a small action, identify obstacles, and find support.</p>\n\n<p>These explorations use curated branches rather than generating every step with AI. Earlier choices influence later options. Personal notes are optional and don’t automatically trigger a model call.</p>\n\n<p>This gives me content I can review and improve consistently. Open-ended AI readings still have a place, but they don’t need to power every interaction.</p>\n\n<p>Completed rituals, explorations, and explicitly saved readings go into a personal Journey timeline. The intention is to give people something worth revisiting: their own thoughts and choices, not just a list of cards.</p>\n\n<p>I also added an opt-in weekly companion board. It shows names, avatars, and activity points, but not private reflections. Points are separate from purchases and don’t depend on someone’s mood or how much they write.</p>\n\n<p>There’s a tension here: even a gentle leaderboard can create comparison. Whether it feels supportive is something I need users to tell me. These are product hypotheses, not proven retention improvements.</p>\n\n<h2>\n  \n  \n  A small stack, with more state than I expected\n</h2>\n\n<p>The stack is Nuxt 3 for the statically generated frontend, a separate Fastify API, SQLite, and DeepSeek for generated readings. Nginx routes API requests to the backend.</p>\n\n<p>For the current scale, I want something I can deploy and troubleshoot on my own. The difficult work has mostly been in the behavior of the application, not the number of services.</p>\n\n<p>A daily card becomes account data once it connects to reflections, history, and rewards. Someone opening the site on their phone should see the same record they started on their laptop. Completion and rewards need consistent server-side state.</p>\n\n<p>Disabling a button helps the interface, but it doesn’t prevent retries or requests from another tab.</p>\n\n<p>Guided exploration saves have a similar problem. If a laptop has already saved the next chapter while a phone still has an older revision, accepting the phone’s write could overwrite newer progress.</p>\n\n<p>Relevant save requests carry a revision. A stale write gets a conflict response, and the frontend keeps the draft while offering to reload saved progress. Identical committed retries return the existing result.</p>\n\n<p>These are easy details to overlook when you only test one browser tab on a reliable connection.</p>\n\n<h2>\n  \n  \n  The content needed versioning, too\n</h2>\n\n<p>One of the more interesting problems came from changing the exploration itself.</p>\n\n<p>Suppose a saved answer says the user selected option <code>1</code>. If I reorder the options next week, that stored value can appear to mean something different.</p>\n\n<p>This is a simplified example of the problem:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>Version 1, option 1: Take time to reflect privately\nVersion 2, option 1: Talk to someone you trust\n</code></pre>\n\n</div>\n\n\n\n<p>The number didn’t change. The user’s apparent answer did.</p>\n\n<p>An exploration therefore retains the content version it started with. Old option meanings need to remain stable, and the live chapter and saved recap need to interpret answers through the same branch logic.</p>\n\n<p>Once copy gives meaning to stored answers, editing it is also a data-compatibility decision.</p>\n\n<p>Weekly reflections brought another state problem. A generation request can time out, a retry can start, and the original request can finish late. Without a guard, that older result could overwrite the newer one.</p>\n\n<p>The implementation uses a generation state, an expiring lease, and a version condition when writing the result. A short database transaction protects state changes; it doesn’t stay open while waiting for the model.</p>\n\n<h2>\n  \n  \n  A paid AI request has more than two outcomes\n</h2>\n\n<p>The demo treated generation as success or failure. Streaming makes that less tidy: a request can return some text, stall, lose its client connection, or finish without usable content.</p>\n\n<p>Deep readings use server-sent events. The server applies a timeout and attempts to cancel the upstream request if the client disconnects early. Only a complete successful result goes into the result cache.</p>\n\n<p>If generation fails after credits have been deducted, the error path restores those in-app credits and provides a local card interpretation. Restoring credits is separate from refunding a payment through the payment provider.</p>\n\n<p>Weekly reflections also have a model-call budget and a local fallback. A free feature still needs a cost boundary.</p>\n\n<p>This isn’t a complete recovery system. For example, a process crash after a deduction cannot be recovered by a <code>catch</code> block in that same process. Durable job state and reconciliation remain areas to improve.</p>\n\n<p>That distinction matters: having an error handler is not the same as having verified recovery from every failure.</p>\n\n<h2>\n  \n  \n  Security extends beyond the prompt\n</h2>\n\n<p>The model generates text. It doesn’t decide account permissions, payment status, or credit balances, and it has no database or payment tools.</p>\n\n<p>That limits the consequences of a manipulated response, but it doesn’t solve prompt injection. User input can still interfere with the intended task, and model output needs to be treated as untrusted content when it reaches the UI.</p>\n\n<p>The service currently has request rate limiting, body-size limits, and account and credit checks before paid generation. These are basic controls, not a claim of comprehensive protection. IP-based limits have limitations, and input handling and output rendering require their own review.</p>\n\n<p>For accounts, passwords use salted hashes, the database stores hashes of session tokens, and session cookies use HttpOnly and SameSite settings, with Secure enabled for production. Verification codes expire and have attempt limits.</p>\n\n<p>Payments have a separate trust boundary. The backend confirms payment status with the provider and verifies webhook signatures. Order-status requests check ownership. Since polling and a webhook can both confirm the same payment, crediting an order must be idempotent.</p>\n\n<p>Privacy also affects ordinary feature decisions. The feedback form doesn’t automatically attach someone’s journal entries. Analytics can record that a reflection was saved without collecting its text.</p>\n\n<p>I still have security work to do. Keeping track of what each component can access—and where personal content goes—has been more useful than treating safety as a few lines in a system prompt.</p>\n\n<h2>\n  \n  \n  Performance and localization added their own work\n</h2>\n\n<p>Static generation lets me serve public pages separately from account APIs, but it doesn’t automatically make the experience fast. Images, animations, authentication checks, and API latency still matter on a phone.</p>\n\n<p>The Journey timeline is paginated, details load on demand, and uploaded avatars are cropped and compressed to WebP in the browser. Streaming also needs the proxy to cooperate: response buffering can hide the incremental output the server is sending.</p>\n\n<p>The interface supports eight languages, while the card data is primarily English and Chinese, with English fallback elsewhere. Those are different levels of localization, and I shouldn’t present them as equivalent.</p>\n\n<p>A recent bug was a good reminder of the extra surface area: an email placeholder contained a literal <code>@</code>, which the i18n message compiler interpreted as special syntax. The JSON was valid, but the page failed. A JSON parse check alone couldn’t catch it.</p>\n\n<p>Even a small copy change needs the right kind of validation.</p>\n\n<h2>\n  \n  \n  Shipping didn’t bring users automatically\n</h2>\n\n<p>I’ve shared the product on Solo, 出海栈, 小众软件, and Indie Hackers. That has given it places to be discovered, but I don’t yet have enough evidence to call any channel a repeatable source of users.</p>\n\n<p>I’m also learning to separate people who enjoy a development story from people who want the product. Developers may find concurrency handling interesting. A potential user wants to know what they can do, how much effort it takes, and whether their writing stays private.</p>\n\n<p>There’s overlap, but a well-read technical post doesn’t prove demand.</p>\n\n<p>My next experiments are technical retrospectives like this one, short product demonstrations, and content built around concrete reflection scenarios. Search is another ongoing task: a readable sitemap and a renderable page are necessary pieces, but I still need public content that answers something people actually look for.</p>\n\n<p>What I want to measure is the path after a visit: does someone complete a first ritual, continue to another exploration chapter, or return a few days later? Knowing where that stops should help me choose between improving the entry flow, the content, or the acquisition channel.</p>\n\n<p>Writing more code is the comfortable option. It gives me a visible result. Asking someone to try the product can end with “I’ll take a look” and nothing else. I’m trying not to mistake the comfort of development for evidence that another feature is needed.</p>\n\n<h2>\n  \n  \n  Where the project is now\n</h2>\n\n<p>Mystic Journey is still early. I’m looking for a small group of people willing to try it and tell me what feels useful, confusing, or unnecessary.</p>\n\n<p>The daily ritual and guided explorations are free; deeper AI readings use credits. If tarot is unfamiliar, you can start with an exploration theme that fits something you’re thinking about.</p>\n\n<p><a href=\"https://tarot.auraflame.tech/?utm_source=devto&amp;utm_medium=article&amp;utm_campaign=demo_to_product\" rel=\"noopener noreferrer\">Try Mystic Journey</a></p>\n\n<p>Specific feedback would help most: where you got stuck, what didn’t make sense, or where you stopped wanting to continue. There’s a feedback form in the app, and comments here are welcome too.</p>\n\n<p>If you’ve taken a side project beyond the demo stage, I’d also like to hear how you found your first users who came back.</p>","score":3},{"source":"https://medium.com/feed/tag/vuejs","sourceHost":"medium.com","title":"Building Accessible Vue Components: A Developer-First Approach","link":"https://medium.com/@guilhermebrunoreis/building-accessible-vue-components-a-developer-first-approach-46a1ae5b0eaa?source=rss------vuejs-5","pubDate":"Thu, 17 Sep 2026 19:25:05 GMT","description":"<div class=\"medium-feed-item\"><p class=\"medium-feed-image\"><a href=\"https://medium.com/@guilhermebrunoreis/building-accessible-vue-components-a-developer-first-approach-46a1ae5b0eaa?source=rss------vuejs-5\"><img src=\"https://cdn-images-1.medium.com/max/1536/1*3bodPDf22NatkUHgh6QNGQ.png\" width=\"1536\"></a></p><p class=\"medium-feed-snippet\">Practical patterns for making accessibility part of component development before issues reach QA or production.</p><p class=\"medium-feed-link\"><a href=\"https://medium.com/@guilhermebrunoreis/building-accessible-vue-components-a-developer-first-approach-46a1ae5b0eaa?source=rss------vuejs-5\">Continue reading on Medium »</a></p></div>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Migrating the GitHub Copilot runtime to Rust, using Copilot","link":"https://github.blog/ai-and-ml/generative-ai/migrating-the-github-copilot-runtime-to-rust-using-copilot/","pubDate":"Thu, 17 Sep 2026 00:26:43 +0000","description":"<p>A rewrite this size wasn't affordable before agents. Here's what porting the Copilot agent runtime to 800,000 lines of production Rust actually took.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/generative-ai/migrating-the-github-copilot-runtime-to-rust-using-copilot/\">Migrating the GitHub Copilot runtime to Rust, using Copilot</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":6},{"source":"https://javascriptweekly.com/rss","sourceHost":"javascriptweekly.com","title":"Functional programming jargon, mapped out","link":"https://javascriptweekly.com/issues/802","pubDate":"Tue, 15 Sep 2026 00:00:00 +0000","description":"<table border=0 cellpadding=0 cellspacing=0 align=\"center\" border=\"0\">\n  <tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <div>    \n    <table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr>\n<td align=\"left\" style=\"padding-left: 4px; font-size: 15px; line-height: 1.48em;\"><p>#​802 — September 15, 2026</p></td>\n<td align=\"right\" style=\"padding-right: 4px; font-size: 15px; line-height: 1.48em;\"><p><a href=\"https://javascriptweekly.com/link/190368/rss\" style=\" color: #3366aa;\">Read on the Web</a></p></td>\n</tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n    \n    <table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0 12px;\"><p>JavaScript Weekly</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/190310/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/yasnr77llfxhnd7g3ffz.jpg\" width=\"640\" style=\"    line-height: 100%;    \"></a>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190310/rss\" title=\"hemanth.github.io\" style=\" color: #3366aa;    font-size: 1.1em; line-height: 1.4em;\">Functional Programming Jargon, Mapped and Explained</a></span> — Currying, purity, functors, monads…? If FP terms ever go over your head, TC39 delegate Hemanth HM's <a href=\"https://javascriptweekly.com/link/190311/rss\" style=\" color: #3366aa;   \">popular jargon reference</a> is now an explorable map of concepts showing how they relate, each with a simple definition and JavaScript example.</p>\n  <p>Hemanth HM </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  <a href=\"https://javascriptweekly.com/link/190309/rss\" style=\" color: #3366aa;   \"><img src=\"https://res.cloudinary.com/cpress/image/upload/c_limit,w_480,h_480,q_auto/copm/17105ae7.png\" width=\"146\" height=\"110\" style=\"padding-top: 12px; padding-left: 12px;     line-height: 100%; \"></a>\n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190309/rss\" title=\"master.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">Stay Sharp in the Age of AI</a></span> — Master.dev instructors build at Anthropic, OpenAI, Netflix, Google, and Stripe. Learn from them with hundreds of courses, live workshops, and expanded AI learning paths. <a href=\"https://javascriptweekly.com/link/190309/rss\" style=\" color: #3366aa;   \">New members get $100 off a yearly membership</a>.</p>\n  <p>Master.dev <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190312/rss\" title=\"philipwalton.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Modern Web Types: TypeScript Support for Newer Web APIs</a></span> — TypeScript's DOM types only include APIs shipped in <em>multiple</em> browser engines, so things like element-scoped <code>startViewTransition</code> and <code>fetchLater</code> throw up errors. <a href=\"https://javascriptweekly.com/link/190313/rss\" style=\" color: #3366aa;   \">modern-web-types</a> is a drop-in <code>lib.dom</code> replacement that adds missing interfaces/members shipped in any single browser engine.</p>\n  <p>Philip Walton (Google) </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190314/rss\" title=\"react.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">React 19.3 Released</a></span> — A significant minor release making view transitions and Fragment Refs stable, adding Trusted Types support, and more. We covered it in more depth in <a href=\"https://javascriptweekly.com/link/190315/rss\" style=\" color: #3366aa;   \">last week's <em>React Status</em></a>.</p>\n  <p>The React Team </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p><strong>IN BRIEF:</strong></p>\n<ul>\n<li>\n<p>🔒 Attackers are <a href=\"https://javascriptweekly.com/link/190316/rss\" style=\" color: #3366aa; font-weight: 500;   \">scanning for exposed Vite dev servers</a> exploiting a <code>server.fs.deny</code> bypass (patched in Vite 7.3.2 and 8.0.5 earlier this year) to grab credentials from <code>.env</code> files. Vite listens on localhost by default, but double check your setup.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190317/rss\" style=\" color: #3366aa; font-weight: 500;   \">Safari 27</a> shipped yesterday (alongside macOS/iOS 27), bringing the module loader rewrite <a href=\"https://javascriptweekly.com/link/190318/rss\" style=\" color: #3366aa; font-weight: 500;   \">we covered last week</a>, so top-level <code>await</code> now works reliably in all major browsers.</p>\n</li>\n<li>\n<p>TC39 meets in Tokyo in two weeks. <a href=\"https://javascriptweekly.com/link/190319/rss\" style=\" color: #3366aa; font-weight: 500;   \">Here's the agenda</a> with three iterator helper follow-ups (<a href=\"https://javascriptweekly.com/link/190320/rss\" style=\" color: #3366aa; font-weight: 500;   \">join</a>, <a href=\"https://javascriptweekly.com/link/190321/rss\" style=\" color: #3366aa; font-weight: 500;   \">includes</a> and <a href=\"https://javascriptweekly.com/link/190322/rss\" style=\" color: #3366aa; font-weight: 500;   \">chunking</a>) all up for Stage 4, plus more.</p>\n</li>\n<li>\n<p>🔒 Starting this Thursday, <a href=\"https://javascriptweekly.com/link/190323/rss\" style=\" color: #3366aa; font-weight: 500;   \">the OpenJS Foundation's CVE team is taking a break</a> till October 6 due to burnout driven by a surge of AI-generated reports. Actively exploited issues will still get a response.</p>\n</li>\n<li>\n<p>🔒 <a href=\"https://javascriptweekly.com/link/190369/rss\" style=\" color: #3366aa; font-weight: 500;   \">npm now places a temporary 72-hour 'security hold'</a> on <em>any</em> account after a successful recovery-code sign-in.</p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p><strong>RELEASES:</strong></p>\n<ul>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190324/rss\" style=\" color: #3366aa; font-weight: 500;   \">pnpm 12.4</a> – The package manager can now manage Rust crates and Python packages alongside npm ones in a single workspace.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190326/rss\" style=\" color: #3366aa; font-weight: 500;   \">Playwright 1.63</a> – Tests can now declare a named <code>lock</code> so those sharing a resource never run concurrently.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190327/rss\" style=\" color: #3366aa; font-weight: 500;   \">Node.js v26.8.2 (Current)</a> and <a href=\"https://javascriptweekly.com/link/190328/rss\" style=\" color: #3366aa; font-weight: 500;   \">v24.21.0 (LTS)</a> – Both include a security release of Undici to fix numerous vulnerabilities.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190372/rss\" style=\" color: #3366aa; font-weight: 500;   \">React Router 8.4</a>, <a href=\"https://javascriptweekly.com/link/190330/rss\" style=\" color: #3366aa; font-weight: 500;   \">Vite 8.3</a>, <a href=\"https://javascriptweekly.com/link/190331/rss\" style=\" color: #3366aa; font-weight: 500;   \">Moment.js 2.31.0</a> (security release).</p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>📖  Articles and Videos</p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190332/rss\" title=\"cel.cs.brown.edu\" style=\" color: #3366aa;    font-size: 1.05em;\">A Design Space Exploration of <code>async</code>/<code>await</code></a></span> — A simple <code>async</code>/<code>await</code> example produces different results in JavaScript, Rust, Python and Swift. Test your mental model with a quiz, then learn about the design choices behind the differences.</p>\n  <p>Gavin Gray </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190336/rss\" title=\"shopify.engineering\" style=\" color: #3366aa;    font-size: 1.05em;\">Native is Now the Future of Mobile at Shopify</a></span> — Shopify is rewriting its React Native mobile apps in Swift/Kotlin. The main argument is coding agents have cut the cost of writing everything twice, while the upsides of native remain.</p>\n  <p>Mustafa Ali (Shopify) </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190335/rss\" title=\"sentry.io\" style=\" color: #3366aa;    font-size: 1.05em;\">Workshop: From an Error to the Logs That Explain It</a></span> — Reading logs next to traces and errors, holding context across services, and cutting the noise.</p>\n  <p>Sentry <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190333/rss\" title=\"flaviocopes.com\" style=\" color: #3366aa;    font-size: 1.05em;\">A Deep Dive into StyleX</a></span> — A hands-on tour of Meta's <a href=\"https://javascriptweekly.com/link/190334/rss\" style=\" color: #3366aa;   \">StyleX</a>, which turns JavaScript style objects into plain atomic CSS at build time, and why it can be a good fit for coding agents in particular.</p>\n  <p>Flavio Copes </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p>📄 <a href=\"https://javascriptweekly.com/link/190370/rss\" style=\" color: #3366aa; font-weight: 500;   \">'Nobody Pays for Open Source: We Can Force Them To'</a> – Laurie Voss ran npm for five years and thinks registries are an untapped lever for funding maintainers. <cite>Laurie Voss</cite></p>\n<p>📄 <a href=\"https://javascriptweekly.com/link/190337/rss\" style=\" color: #3366aa; font-weight: 500;   \">Anecdotally, Programmers Dislike <code>reduce</code></a> – <code>map</code> and <code>filter</code> sail through code review, but <code>reduce</code> draws complaints. Evan has a few theories why. <cite>Evan Hahn</cite></p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>🛠 Code &amp; Tools</p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190341/rss\" title=\"snapdom.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">SnapDOM 3.0: Turn DOM Elements Into Images, Canvas and More</a></span> — Zero-dependency <a href=\"https://javascriptweekly.com/link/190342/rss\" style=\" color: #3366aa;   \">html2canvas</a> alternative with support for pseudo-elements and Shadow DOM. <a href=\"https://javascriptweekly.com/link/190343/rss\" style=\" color: #3366aa;   \">v3.0</a> adds 'incremental recapture' for faster repeat captures, automatic web font embedding, and <code>fromString()</code> for turning raw HTML markup into images.</p>\n  <p>Zumerlab </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190344/rss\" title=\"www.tigerdata.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Your App Didn't Get Slower. Your Data Got Bigger</a></span> — TimescaleDB extends Postgres so queries stay fast at scale, even as your data grows. <a href=\"https://javascriptweekly.com/link/190344/rss\" style=\" color: #3366aa;   \">Get $1000 credit to start</a>.</p>\n  <p>Tiger Data (creators of TimescaleDB) <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/190345/rss\" title=\"docs.fallow.tools\" style=\" color: #3366aa;    font-size: 1.05em;\">Fallow: Codebase Intelligence for TypeScript and JavaScript</a></span> — A fast, zero-config Rust binary that finds dead code, duplication, circular dependencies and complexity hotspots in JS/TS projects. It's also pitched as a deterministic check for AI agent coding loops. <a href=\"https://javascriptweekly.com/link/190346/rss\" style=\" color: #3366aa;   \">GitHub repo</a>.</p>\n  <p>Bart Waardenburg </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><span>🎨</span> <a href=\"https://javascriptweekly.com/link/190347/rss\" title=\"mattstromawn.com\" style=\" color: #3366aa;    font-size: 1.05em;\">category-colors: Generate 'Least Wrong' Color Palettes for Charts</a></span> — Matt devised <a href=\"https://javascriptweekly.com/link/190348/rss\" style=\" color: #3366aa;   \">this algorithm for picking categorical chart colors</a> in 2022 while design director at Stripe. It's now available as <a href=\"https://javascriptweekly.com/link/190349/rss\" style=\" color: #3366aa;   \">an npm package</a>, and you can <a href=\"https://javascriptweekly.com/link/190371/rss\" style=\" color: #3366aa;   \">try it out on the Web here</a>.</p>\n  <p>Matt Ström-Awn </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<ul>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190350/rss\" style=\" color: #3366aa; font-weight: 500;   \">React DevTools 8.0</a> (<a href=\"https://javascriptweekly.com/link/190351/rss\" style=\" color: #3366aa; font-weight: 500;   \">Chrome Web Store</a>) – The Suspense tab is now on by default, the Timeline profiler is gone (use your browser's <em>Performance</em> panel instead), and inspecting a DOM node now shows its matching React component.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190325/rss\" style=\" color: #3366aa; font-weight: 500;   \">Zod 4.6</a> – Adds <code>.validate()</code>, a boolean check that skips building errors and is up to 35x faster than <code>.safeParse().success</code> on invalid input with compiled schemas.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190352/rss\" style=\" color: #3366aa; font-weight: 500;   \">Javet 6.0</a> – Embed Node.js v26's runtime in the JVM for full interop with Java.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/190353/rss\" style=\" color: #3366aa; font-weight: 500;   \">parse-xml 5.0</a> – The fast, compliant XML parser <a href=\"https://javascriptweekly.com/link/190354/rss\" style=\" color: #3366aa; font-weight: 500;   \">goes ESM-only</a>.</p>\n</li>\n<li>\n<p>🕹️ <a href=\"https://javascriptweekly.com/link/190355/rss\" style=\" color: #3366aa; font-weight: 500;   \">n64js 1.0</a> – A Nintendo 64 emulator in pure JavaScript.</p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px;\">\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n\t<p>📰 Classifieds</p>\n  </td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p>💻 <a href=\"https://javascriptweekly.com/link/190356/rss\" style=\" color: #3366aa; font-weight: 500;   \">Free live coding workshop, Sept 16</a>. Build signup protection with Fingerprint that catches fake signups, no matter how they cover their tracks.</p>\n \n<p>Flaky tests slowing down dev? <a href=\"https://javascriptweekly.com/link/190357/rss\" style=\" color: #3366aa; font-weight: 500;   \">Meticulous</a> gives engineers confidence to ship faster by autonomously testing every edge case of your web app.</p>\n \n<p>⚡ <a href=\"https://javascriptweekly.com/link/190358/rss\" style=\" color: #3366aa; font-weight: 500;   \">Zuplo</a> puts every API, AI, and MCP request behind one gateway. Route traffic, guard your MCP servers, and cap your AI costs. <a href=\"https://javascriptweekly.com/link/190358/rss\" style=\" color: #3366aa; font-weight: 500;   \">Try it free</a>.</p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>📢  Elsewhere in the ecosystem</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/190359/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/wwfey07te8tmpqvviapc.jpg\" width=\"640\" style=\"    line-height: 100%;      \"></a>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<ul>\n<li>\n<p>🕹️ This year's <a href=\"https://javascriptweekly.com/link/190359/rss\" style=\" color: #3366aa; font-weight: 500;   \">js13kGames</a> entries are in with a record <a href=\"https://javascriptweekly.com/link/190360/rss\" style=\" color: #3366aa; font-weight: 500;   \">317 games squeezed into 13KB each</a>. Frank Force's <a href=\"https://javascriptweekly.com/link/190361/rss\" style=\" color: #3366aa; font-weight: 500;   \">SP13KTRA</a> racer and the <a href=\"https://javascriptweekly.com/link/190362/rss\" style=\" color: #3366aa; font-weight: 500;   \">Hornbound</a> roguelike are good places to start, and you can dig around the public repos of every project too.</p>\n</li>\n<li>\n<p>Chris Coyier rounds up <a href=\"https://javascriptweekly.com/link/190365/rss\" style=\" color: #3366aa; font-weight: 500;   \">new and emerging HTML features worth knowing about</a>, like the <code>&lt;geolocation&gt;</code> and <code>&lt;install&gt;</code> elements, as well as HTML-in-Canvas and improvements to <code>&lt;select&gt;</code> customization.</p>\n</li>\n<li>\n<p>The first <a href=\"https://javascriptweekly.com/link/190363/rss\" style=\" color: #3366aa; font-weight: 500;   \">Three.js Conference</a> took place in Paris last week, and Codrops has <a href=\"https://javascriptweekly.com/link/190364/rss\" style=\" color: #3366aa; font-weight: 500;   \">a live-blog writeup of both days</a>, including Mr.doob's talk. No recordings yet.</p>\n</li>\n<li>\n<p>Tailwind Labs, the team behind Tailwind CSS, <a href=\"https://javascriptweekly.com/link/190366/rss\" style=\" color: #3366aa; font-weight: 500;   \">is joining Shopify</a>. Tailwind CSS is to remain maintained and open source – phew! 😅</p>\n</li>\n</ul>\n<p></p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n</div>\n  </td></tr>\n</table>\n\n\n\n\n<img src=\"https://javascriptweekly.com/open/802/rss\" width=\"1\" height=\"1\" />","score":4},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Marketing ops as code: Automating events from planning to follow-up on GitHub","link":"https://github.blog/ai-and-ml/github-copilot/marketing-ops-as-code-automating-events-from-planning-to-follow-up-on-github/","pubDate":"Fri, 11 Sep 2026 18:26:10 +0000","description":"<p>If you can write down how you do your work, you can automate it. Here's what I did to support GitHub's APAC marketing team.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/github-copilot/marketing-ops-as-code-automating-events-from-planning-to-follow-up-on-github/\">Marketing ops as code: Automating events from planning to follow-up on GitHub</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"GitHub availability report: August 2026","link":"https://github.blog/news-insights/company-news/github-availability-report-august-2026/","pubDate":"Thu, 10 Sep 2026 02:05:17 +0000","description":"<p>In August, we experienced five incidents that resulted in degraded performance across GitHub services.</p>\n<p>The post <a href=\"https://github.blog/news-insights/company-news/github-availability-report-august-2026/\">GitHub availability report: August 2026</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":6},{"source":"https://dev.to/feed/tag/nuxt","sourceHost":"dev.to","title":"Where Laravel Is Heading in 2026: AI, Vue, Nuxt, and Product Development","link":"https://dev.to/kavitasystems/where-laravel-is-heading-in-2026-ai-vue-nuxt-and-product-development-5290","pubDate":"Tue, 08 Sep 2026 09:48:06 +0000","description":"<p>An AI assistant can help generate an interface quickly. Getting that interface ready for real users still involves permissions, validation, background work, deployment, monitoring, and clear feedback when something fails.</p>\n\n<p>Laravel’s recent updates address more of that journey.</p>\n\n<p>Across its May–August 2026 releases and early September engineering posts, a direction emerges: Laravel is connecting development with AI, application infrastructure, and the tools needed to operate a product.</p>\n\n<p>From a design engineering perspective, the interesting question is how this changes the way we build and maintain applications.</p>\n\n<p><em>This article covers official Laravel announcements available on September 7, 2026. The practical recommendations are my interpretation of those changes.</em></p>\n\n<h2>\n  \n  \n  1. Understanding Laravel’s AI tools\n</h2>\n\n<p>Several tools now support different parts of the development and product lifecycle.</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Tool</th>\n<th>What it does</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Laravel Boost</strong></td>\n<td>Gives coding agents Laravel knowledge and project context</td>\n</tr>\n<tr>\n<td><strong>Laravel PAO</strong></td>\n<td>Makes development tool output easier for agents to process</td>\n</tr>\n<tr>\n<td><strong>Laravel AI SDK</strong></td>\n<td>Provides APIs for AI features inside an application</td>\n</tr>\n<tr>\n<td><strong>Laravel MCP</strong></td>\n<td>Provides MCP server and client capabilities for connecting tools</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>For example, PAO produces compact JSON for supported testing and analysis tools and removes unnecessary decoration from Artisan output.</p>\n\n<p>An agent can more easily identify a failed assertion, the affected file, or an analysis error. Laravel introduced PAO in May and included it in new applications as a development dependency.</p>\n\n<p>The distinction between these tools matters. Boost and PAO improve how we develop applications. The AI SDK and MCP help us build applications that use AI and connected tools.</p>\n\n<h2>\n  \n  \n  2. Project knowledge becomes part of the workflow\n</h2>\n\n<p>A mature application contains decisions that are easy to miss:</p>\n\n<ul>\n<li>Where business logic belongs.</li>\n<li>How shared components should be reused.</li>\n<li>How values are stored and validated.</li>\n<li>Which operations require authorization.</li>\n<li>Which architectural boundaries the team has chosen.</li>\n</ul>\n\n<p>Boost’s convention extraction workflow examines existing code, gathers evidence, and proposes project rules for developer review.</p>\n\n<p>Approved rules become files in the repository. The team can inspect them, review changes, and maintain them alongside the implementation.</p>\n\n<p>The storage approach is deliberately simple: Markdown rules, a generated index, and selective retrieval.</p>\n\n<p>Laravel’s engineering post explains why a semantic search layer added too much complexity for a small collection of conventions. The authors also acknowledge that controlled evaluation of the alternatives remains future work.</p>\n\n<p>For teams working with design systems, this suggests a useful practice: <strong>document the decisions an agent needs to preserve.</strong></p>\n\n<p>For example:</p>\n\n<ul>\n<li>Reuse existing form components.</li>\n<li>Apply the project’s spacing and color tokens.</li>\n<li>Follow established validation and error states.</li>\n<li>Preserve keyboard navigation and focus behavior.</li>\n<li>Check existing patterns before introducing a new component.</li>\n</ul>\n\n<p>These are examples of rules a team could define. Their value comes from describing the actual project.</p>\n\n<h2>\n  \n  \n  3. AI code quality needs a broader definition\n</h2>\n\n<p>In July, the Boost team described a shift toward measuring two things:</p>\n\n<ol>\n<li>How well generated code follows Laravel conventions.</li>\n<li>How many tokens it takes to reach a correct result.</li>\n</ol>\n\n<p>Passing an evaluation suite provides a useful baseline, but it tells only part of the story.</p>\n\n<p>For a product team, I would extend the review to a few practical questions:</p>\n\n<ul>\n<li>Does the change fit the application’s architecture?</li>\n<li>Does it preserve access rules and validation?</li>\n<li>Does it reuse existing UI patterns?</li>\n<li>Can another developer understand and maintain it?</li>\n<li>Does the complete user journey still work?</li>\n</ul>\n\n<p>These are familiar engineering questions. They become more valuable as generating code becomes faster.</p>\n\n<h2>\n  \n  \n  4. Vue and Nuxt have clearer deployment options\n</h2>\n\n<p>Laravel Cloud added support for deploying Nuxt and Next.js applications in July.</p>\n\n<p>A frontend and Laravel backend can share a repository while running as separate Cloud applications. Each application has its own environment variables, domains, and scaling settings.</p>\n\n<p>Meanwhile, the official Vue starter kit currently combines Vue 3, TypeScript, Inertia 3, and shadcn-vue.</p>\n\n<p>For teams using Vue, this leaves two useful architectural options:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Architecture</th>\n<th>When I would consider it</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Laravel + Vue + Inertia</strong></td>\n<td>A dashboard, portal, or SaaS application whose interface closely follows Laravel’s application flow</td>\n</tr>\n<tr>\n<td><strong>Laravel API + Nuxt</strong></td>\n<td>An independently developed frontend, a content or commerce experience, or a product serving several API clients</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>These are starting points for a decision.</p>\n\n<p>Team ownership, rendering requirements, deployment needs, and existing code should determine the final choice. Supporting both approaches on one hosting platform makes that choice easier to separate from infrastructure preferences.</p>\n\n<h2>\n  \n  \n  5. AI features create new UX responsibilities\n</h2>\n\n<p>Laravel’s AI SDK supports agents with tools, delegation to subagents, and human approval for selected tool calls.</p>\n\n<p>Its documentation describes persisting a conversation so an action can pause for approval and resume later.</p>\n\n<p>Consider a support assistant that prepares a change to a customer’s subscription.</p>\n\n<p>The interface needs to explain:</p>\n\n<ul>\n<li>What the assistant found.</li>\n<li>What it proposes to change.</li>\n<li>Which account and subscription will be affected.</li>\n<li>Whether the action is waiting for approval or already running.</li>\n<li>What succeeded and what failed.</li>\n<li>What the user can edit, reject, or retry.</li>\n</ul>\n\n<p><strong>The approval screen, progress state, and result history are part of the product’s reliability.</strong></p>\n\n<p>For designers and frontend developers, this expands the work beyond a chat interface. We need to design how users understand, control, and recover from automated actions.</p>\n\n<h2>\n  \n  \n  6. Infrastructure supports longer product workflows\n</h2>\n\n<p>Document processing, exports, media jobs, and AI tasks can take longer than a normal page request.</p>\n\n<p>Managed queues on Laravel Cloud run workers separately from application compute and scale them according to queue pressure. The dashboard exposes failed jobs and retry actions.</p>\n\n<p>This supports a useful product pattern:</p>\n\n<ol>\n<li>Accept the user’s request.</li>\n<li>Confirm that processing has started.</li>\n<li>Run the work in the background.</li>\n<li>Show progress or completion.</li>\n<li>Provide a recovery path if something fails.</li>\n</ol>\n\n<p>Laravel also rebuilt Flex scale-to-zero around checkpoint and restore, reporting wake times below 500 milliseconds.</p>\n\n<p>That is a platform claim rather than a measurement from my own application. The goal is useful for demos, staging environments, and products with intermittent traffic: reduce the resources running while nothing is happening.</p>\n\n<p>Nightwatch has also expanded its MCP interface to expose performance information alongside exceptions. This gives assistants more evidence when investigating slow routes, queries, and jobs.</p>\n\n<h2>\n  \n  \n  7. Everyday framework improvements still matter\n</h2>\n\n<p>Alongside AI and hosting, Laravel continues to improve the practical work of maintaining applications.</p>\n\n<p>June brought bulk job dispatch and support for PostgreSQL transaction poolers.</p>\n\n<p>The summer Laracon overview covers additions such as:</p>\n\n<ul>\n<li>Inertia DevTools.</li>\n<li>Refreshable locks.</li>\n<li>Debounced jobs.</li>\n<li>Image manipulation.</li>\n<li>Local development diagnostics.</li>\n</ul>\n\n<p>August extended semantic and hybrid search in Scout and vector support for MariaDB. It also introduced a read-through filesystem for moving between storage disks, plus more narrowly scoped Cloud API tokens.</p>\n\n<p>These changes help with the less visible work behind a product: finding relevant information, managing repeated work, moving data, and limiting automation to the resources it needs.</p>\n\n<h2>\n  \n  \n  What I would put into practice\n</h2>\n\n<p>The direction I see is a shorter, more connected path from a product decision to a running application.</p>\n\n<p>Framework packages, development tools, and hosted services each contribute to that path. They remain separate choices to evaluate against a project’s needs.</p>\n\n<p>For a team adopting these capabilities, I would start with one complete workflow:</p>\n\n<ul>\n<li>A clear Laravel architecture.</li>\n<li>Reusable interface components and design tokens.</li>\n<li>Documented project rules for the coding agent.</li>\n<li>One useful AI feature with appropriate user controls.</li>\n<li>Background processing where it is needed.</li>\n<li>Monitoring for behavior, failures, and cost.</li>\n</ul>\n\n<p>Then measure the outcome.</p>\n\n<p>Can users complete the task? Does the interface explain what happened? Can the team diagnose failures and change the implementation confidently?</p>\n\n<p>Those answers will tell us how much value the new tools create.</p>\n\n\n\n\n<p>Which part would improve your current project most: better context for coding agents, simpler deployment, or AI actions with a clear approval flow?</p>\n\n<p>For help building, modernizing, or extending a Laravel application, explore <a href=\"https://kavitasystems.com/our-services/laravel-development-company\" rel=\"noopener noreferrer\">Laravel development services at Kavita Systems</a>.</p>","score":7},{"source":"https://dev.to/feed/tag/nuxt","sourceHost":"dev.to","title":"Prerendering a multilingual Nuxt game catalog without shipping the database","link":"https://dev.to/mno_tao_236ab4649edf4cf9f/prerendering-a-multilingual-nuxt-game-catalog-without-shipping-the-database-36pf","pubDate":"Sun, 06 Sep 2026 01:00:00 +0000","description":"<p>A game catalog looks like a client-side application: filters, search, cards, detail pages, and playable iframes. It is still a poor reason to ship the complete content database and ask every crawler—or every phone—to reconstruct the page after JavaScript loads.</p>\n\n<p>I recently localized a Nuxt catalog with 153 games into four languages. The public result is 676 indexable routes, but the architecture has three strict properties:</p>\n\n<ol>\n<li>every indexable route is prerendered as complete HTML;</li>\n<li>missing localized content fails the build instead of falling back to English;</li>\n<li>the browser does not receive Nuxt Content’s SQLite/WASM engine or the full game records.</li>\n</ol>\n\n<p>Here is how those pieces fit together.</p>\n\n<h2>\n  \n  \n  Make the route set explicit\n</h2>\n\n<p>Relying only on a crawler means an accidentally missing link can remove a page from the static build. The catalog already has a source of truth, so use it to produce the complete route list.</p>\n\n<p>The base set contains:</p>\n\n<ul>\n<li>home, popular, new, search, and four site/legal pages;</li>\n<li>one page per category;</li>\n<li>one page per game.</li>\n</ul>\n\n<p>With 153 games and eight categories, that is 169 base routes. A locale mapping applies no prefix to English and <code>/id/</code>, <code>/it/</code>, or <code>/pt-br/</code> to the other languages. The same English slugs remain stable after the prefix.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>169 base routes × 4 locales = 676 indexable routes\n</code></pre>\n\n</div>\n\n\n\n<p>Localized 404 pages are also prerendered, but they are <code>noindex</code> and do not enter the sitemap.</p>\n\n<p>This list feeds Nitro prerendering and the sitemap generator. The sitemap is not treated as evidence that pages probably exist; a post-build check resolves every <code>&lt;loc&gt;</code> to a generated HTML file.</p>\n\n<h2>\n  \n  \n  Keep one source of truth for product facts\n</h2>\n\n<p>Game titles, iframe URLs, developers, dates, embed types, and stable slugs are product facts. Translators should not rewrite them.</p>\n\n<p>Descriptions, objectives, controls, tips, category copy, and legal text are localized. A generation step combines those translations with the factual fields and writes the Markdown documents Nuxt Content will consume during the build.</p>\n\n<p>For each non-English locale, the generator expects:</p>\n\n<ul>\n<li>153 game documents;</li>\n<li>eight category documents;</li>\n<li>four site/legal documents.</li>\n</ul>\n\n<p>That is 165 documents per locale and 495 localized Markdown files in total. Missing keys, extra slugs, duplicate entries, or fact drift are build errors.</p>\n\n<p>The key rule is simple: <strong>an indexable localized route cannot silently borrow English body content</strong>. A visible fallback is useful in application chrome; it is dangerous when it creates a page advertised to search engines as a different language.</p>\n\n<h2>\n  \n  \n  Localize the page, not the embedded game\n</h2>\n\n<p>The catalog can translate its navigation, metadata, controls guide, objectives, and warnings. It does not own every embedded game’s UI.</p>\n\n<p>That boundary should be explicit. The page language changes, while the game iframe may remain in English. Trying to imply otherwise creates misleading metadata and support expectations.</p>\n\n<p>The language switcher should preserve the current route:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>/game/ember-vault/\n/it/game/ember-vault/\n/pt-br/game/ember-vault/\n</code></pre>\n\n</div>\n\n\n\n<p>Do not redirect based on <code>Accept-Language</code> or browser settings. Automatic redirects make URLs unstable for crawlers and surprising for people who intentionally chose another language.</p>\n\n<h2>\n  \n  \n  Generate SEO signals from the same locale model\n</h2>\n\n<p>Every page needs a self-referencing canonical and a complete alternate set:</p>\n\n<ul>\n<li>\n<code>en</code>;</li>\n<li>\n<code>id-ID</code>;</li>\n<li>\n<code>it-IT</code>;</li>\n<li>\n<code>pt-BR</code>;</li>\n<li>\n<code>x-default</code> pointing to English.</li>\n</ul>\n\n<p>The document <code>lang</code>, Open Graph locale, visible copy, and Schema <code>inLanguage</code> must agree. The sitemap repeats the same alternates.</p>\n\n<p>This is a good place for a deterministic verifier. For all 676 HTML files, check:</p>\n\n<ul>\n<li>expected <code>html lang</code>;</li>\n<li>exact canonical URL;</li>\n<li>all four <code>hreflang</code> entries plus <code>x-default</code>;</li>\n<li>localized Open Graph locale;</li>\n<li>Schema language;</li>\n<li>no unresolved message keys;</li>\n<li>no known English section headings on non-English pages.</li>\n</ul>\n\n<p>Counting is surprisingly valuable. If the expected route count is 676 and the verifier saw 675, the build fails before deployment.</p>\n\n<h2>\n  \n  \n  Split card data from detail data\n</h2>\n\n<p>The full game record contains fields that a grid never needs: iframe URL, long controls, features, developer information, and related-game details. Importing that object into a shared composable can put the entire catalog into a client chunk.</p>\n\n<p>A build step projects a slim card record containing only fields used by search and grids, such as:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>slug, title, description, category, tags,\nthumbnail, embed type, difficulty, updated date, popular\n</code></pre>\n\n</div>\n\n\n\n<p>Detail fields stay server/build-side. The detail route receives one game during prerendering and serializes only what that page needs. Legal and editorial pages do not preload the card catalog merely because they share a layout.</p>\n\n<p>Add a leak test with a known detail-only iframe URL. If that marker appears in a shared JavaScript chunk, the split has regressed.</p>\n\n<h2>\n  \n  \n  Remove the client database when all queries are prerendered\n</h2>\n\n<p>Nuxt Content can ship a SQLite/WASM client for browser-side queries. A fully static catalog does not need it if every content query runs during prerender and the result is hydrated from the page payload.</p>\n\n<p>The production build aliases the client database module to a stub that must never execute. Post-build checks then fail if they find:</p>\n\n<ul>\n<li>SQLite WASM or worker artifacts;</li>\n<li>public content database dumps;</li>\n<li>a runtime fetch for the full catalog;</li>\n<li>old code that overwrites the prerendered description after hydration.</li>\n</ul>\n\n<p>The last item protects more than performance. Runtime replacement can show one description without JavaScript and a shorter, different one with JavaScript, which makes accessibility and indexing unpredictable.</p>\n\n<h2>\n  \n  \n  Put budgets around the remaining JavaScript\n</h2>\n\n<p>Internationalization has a real bundle cost. Lazy locale dictionaries help, but only if the application does not preload all languages on every route.</p>\n\n<p>Track both the largest chunk and total emitted JavaScript. Also scan locale chunks for markers from more than one language. A budget is not a universal performance score; it is a tripwire against accidental catalog or dictionary duplication.</p>\n\n<p>The browser check should visit a localized game page directly, before navigating from English. That catches implementations that work only after a locale bundle has already been loaded.</p>\n\n<h2>\n  \n  \n  Test the game boundary too\n</h2>\n\n<p>A perfectly localized wrapper is still broken if the play button cannot launch its iframe. For a small set of representative self-hosted games, browser checks should:</p>\n\n<ul>\n<li>open the localized detail route;</li>\n<li>verify localized initial HTML;</li>\n<li>launch the iframe;</li>\n<li>send a real keyboard or touch input;</li>\n<li>observe game progress rather than elapsed time;</li>\n<li>verify pause, restart, focus, and mobile sizing.</li>\n</ul>\n\n<p>For third-party embeds, record that availability is an external dependency. Do not translate or bundle their binaries as if they were catalog content.</p>\n\n<p>The reference implementation discussed here is <a href=\"https://sonotap.online/\" rel=\"noopener noreferrer\">SonoTap</a>. The transferable pattern is to use static generation as an integrity boundary: routes, translations, metadata, content, and client payloads are all artifacts that a build can count and reject.</p>","score":5},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Project HydraFusion: Frontier quality via multi-model orchestration","link":"https://github.blog/ai-and-ml/github-copilot/project-hydrafusion-frontier-quality-via-multi-model-orchestration/","pubDate":"Fri, 04 Sep 2026 16:04:14 +0000","description":"<p>In controlled offline evaluations, HydraFusion’s selective coding workflows matched or exceeded the evaluated Opus 5 baseline while reducing estimated workflow cost. Now available as a research preview in GitHub Copilot.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/github-copilot/project-hydrafusion-frontier-quality-via-multi-model-orchestration/\">Project HydraFusion: Frontier quality via multi-model orchestration</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Decoding the new AI lingo: Loops, harnesses, squads, hill climbing&#8230; oh my!","link":"https://github.blog/ai-and-ml/decoding-the-new-ai-lingo-loops-harnesses-squads-hill-climbing-oh-my/","pubDate":"Wed, 02 Sep 2026 21:00:00 +0000","description":"<p>From loop engineering to harnesses, squads, and open weights, the GitHub Podcast breaks down the AI terms showing up in developer conversations.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/decoding-the-new-ai-lingo-loops-harnesses-squads-hill-climbing-oh-my/\">Decoding the new AI lingo: Loops, harnesses, squads, hill climbing&#8230; oh my!</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"How we make AI coding more cost efficient without sacrificing task quality","link":"https://github.blog/ai-and-ml/github-copilot/how-we-make-ai-coding-more-cost-efficient-without-sacrificing-task-quality/","pubDate":"Wed, 02 Sep 2026 18:00:00 +0000","description":"<p>Why shorter outputs can cost more, and how GitHub Copilot reduces wasted work across the complete coding task.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/github-copilot/how-we-make-ai-coding-more-cost-efficient-without-sacrificing-task-quality/\">How we make AI coding more cost efficient without sacrificing task quality</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://javascriptweekly.com/rss","sourceHost":"javascriptweekly.com","title":"How to fit Minesweeper into 247 bytes","link":"https://javascriptweekly.com/issues/800","pubDate":"Tue, 1 Sep 2026 00:00:00 +0000","description":"<table border=0 cellpadding=0 cellspacing=0 align=\"center\" border=\"0\">\n  <tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <div>    \n    <table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr>\n<td align=\"left\" style=\"padding-left: 4px; font-size: 15px; line-height: 1.48em;\"><p>#​800 — September 1, 2026</p></td>\n<td align=\"right\" style=\"padding-right: 4px; font-size: 15px; line-height: 1.48em;\"><p><a href=\"https://javascriptweekly.com/link/189716/rss\" style=\" color: #3366aa;\">Read on the Web</a></p></td>\n</tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\"><p><b>Issue 800!</b> I'm not doing anything to celebrate, but I know many of you have been here for <em>years</em>, so thanks for your continued support. Maybe I'll do something for issue 1000, though at current velocity that'll be late 2030... ;-)<br>__<br><em>Your editor, Peter Cooper</em></p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n    \n    <table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0 12px;\"><p>JavaScript Weekly</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/189671/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/bx17eefknqp64qnr1fet.jpg\" width=\"640\" style=\"    line-height: 100%;    \"></a>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189671/rss\" title=\"yui.dev\" style=\" color: #3366aa;    font-size: 1.1em; line-height: 1.4em;\">The Depths of JavaScript: Minesweeper in 247 Bytes</a></span> — An analysis of a playable 8x8 Minesweeper implementation in one line of JavaScript, complete with flags and cascading blank cells. The author's 658-byte version is impressive enough, but this post covers the tricks to make it 62% smaller than that!</p>\n  <p>yui and DNEK </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  <a href=\"https://javascriptweekly.com/link/189670/rss\" style=\" color: #3366aa;   \"><img src=\"https://res.cloudinary.com/cpress/image/upload/c_limit,w_480,h_480,q_auto/copm/8de3904f.png\" width=\"160\" height=\"91\" style=\"padding-top: 12px; padding-left: 12px;     line-height: 100%; \"></a>\n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189670/rss\" title=\"coderabbit.link\" style=\" color: #3366aa;    font-size: 1.05em;\">Stop Reviewing PRs in the Order They Arrived. Review in the Order That Matters</a></span> — CodeRabbit Triage is a reviewer-first prioritization layer for the PR queue: a self-updating, cross-repository inbox that tells you which pull request to review next, how deeply to review it and which ones may be safe to close.</p>\n  <p>CodeRabbit <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189717/rss\" title=\"remix.run\" style=\" color: #3366aa;    font-size: 1.05em;\">Remix 3 RC: A Full-Stack Framework with No React Required</a></span> — Remix's big rewrite from its previous life as a React framework into an independent full-stack framework is largely done. This post makes a good pitch, but even better is the <a href=\"https://javascriptweekly.com/link/189718/rss\" style=\" color: #3366aa;   \">all-new homepage</a> explaining how Remix offers everything you need to build a modern webapp in 'a single package'.</p>\n  <p>Brooks Lybrand </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\"><p>💡 The final release is due on October 2 but it's more than ready to start working with.</p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189672/rss\" title=\"pnpm.io\" style=\" color: #3366aa;    font-size: 1.05em;\">pnpm 12: Rewritten in Rust, Same Commands and Lockfile</a></span> — The Rust rewrite is stable, and nearly all of v11's commands, flags and lockfile format carry over with only a <a href=\"https://javascriptweekly.com/link/189673/rss\" style=\" color: #3366aa;   \">handful of changes</a>. npm's <code>latest</code> tag still points to pnpm 11, though, so you'd need <code>pnpm self-update next-12</code> to upgrade.</p>\n  <p>Zoltan Kochan </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p><strong>IN BRIEF:</strong></p>\n<ul>\n<li>\n<p>TypeScript has <a href=\"https://javascriptweekly.com/link/189719/rss\" style=\" color: #3366aa; font-weight: 500;   \">landed support for import-attribute-aware ambient modules</a>. Declarations such as  <code>declare module \"*\" with { type: \"css\" }</code> can now match imports carrying that attribute.</p>\n</li>\n<li>\n<p>Dani Sandoval rounds up <a href=\"https://javascriptweekly.com/link/189676/rss\" style=\" color: #3366aa; font-weight: 500;   \">what's new in the Svelte world this month</a> including Svelte 5.57's new <code>SvelteMap</code> methods, SvelteKit 3 reaching RC, and an <code>ai-tools</code> add-on for the <code>sv</code> CLI.</p>\n</li>\n<li>\n<p>Sarah Rainsberger rounds up <a href=\"https://javascriptweekly.com/link/189677/rss\" style=\" color: #3366aa; font-weight: 500;   \">what's new in Astro this month</a> too, including Matthew Phillips taking over as 'Project Steward', and a new <a href=\"https://javascriptweekly.com/link/189729/rss\" style=\" color: #3366aa; font-weight: 500;   \">Astro Playground</a>.</p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p><strong>RELEASES:</strong></p>\n<ul>\n<li>\n<p>⭐ <a href=\"https://javascriptweekly.com/link/189678/rss\" style=\" color: #3366aa; font-weight: 500;   \">Rspack 2.2, Rsbuild 2.2, and Rslint 0.9</a> – Faster builds and HMR, shorter module IDs, and Node.js chunk splitting on by default in Rsbuild. Notably, Rslint now implements all rules and presets from <code>@typescript-eslint</code>.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/189720/rss\" style=\" color: #3366aa; font-weight: 500;   \">Cypress 16</a>, <a href=\"https://javascriptweekly.com/link/189681/rss\" style=\" color: #3366aa; font-weight: 500;   \">NestJS 12</a>, <a href=\"https://javascriptweekly.com/link/189682/rss\" style=\" color: #3366aa; font-weight: 500;   \">Vue 3.6 RC6</a>, <a href=\"https://javascriptweekly.com/link/189683/rss\" style=\" color: #3366aa; font-weight: 500;   \">Deno 2.9.6</a>, <a href=\"https://javascriptweekly.com/link/189684/rss\" style=\" color: #3366aa; font-weight: 500;   \">Mocha 12.0</a>, <a href=\"https://javascriptweekly.com/link/189685/rss\" style=\" color: #3366aa; font-weight: 500;   \">Electron 44</a> (mentioned last week but there's now an official post).</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/189686/rss\" style=\" color: #3366aa; font-weight: 500;   \">Node.js v26.8.0 (Current)</a> and <a href=\"https://javascriptweekly.com/link/189687/rss\" style=\" color: #3366aa; font-weight: 500;   \">Node.js v24.20.0 (LTS)</a></p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>📖  Articles and Videos</p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189691/rss\" title=\"resend.com\" style=\" color: #3366aa;    font-size: 1.05em;\">The Browser's New Email Verification API</a></span> — A look at a WICG proposal to get rid of the 'go and check your inbox' headache with the browser and email provider using a token the server verifies on submit. Chrome origin trial and Gmail only for now.</p>\n  <p>Phil Nash </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189692/rss\" title=\"lovable.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">Lovable's Migration from Next.js to TanStack Start</a></span> — How Lovable migrated a ~850K LOC app from Next.js to TanStack Start while running both in parallel behind a proxy, with a shared folder of code between them.</p>\n  <p>Alexander Lebedev (Lovable) </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189689/rss\" title=\"svar.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">What We Learned Building a Data Grid in React, Vue, and Svelte</a></span> — SVAR ships the same data grid for React, Vue and Svelte, and says the most crucial, performance-impacting elements remain the same whatever the framework.</p>\n  <p>Maksim Kozhukh (SVAR) </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p>📄 <a href=\"https://javascriptweekly.com/link/189693/rss\" style=\" color: #3366aa; font-weight: 500;   \">The Problem with Concurrent Linter Fixes</a> – Applying several autofixes at once can produce broken code that a \"fix-then-reanalyze\" loop wouldn't. <cite>Jeroen Engels</cite></p>\n<p>📄 <a href=\"https://javascriptweekly.com/link/189694/rss\" style=\" color: #3366aa; font-weight: 500;   \">Bumping the Major Version of Your JavaScript Library is User Hostile</a>  <cite>James Healy</cite></p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>🛠 Code &amp; Tools</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/189721/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/eo9krbjjdoksfuqz7faw.jpg\" width=\"640\" style=\"    line-height: 100%;      \"></a>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189721/rss\" title=\"uppy.io\" style=\" color: #3366aa;    font-size: 1.05em;\">Uppy 6.0: A Modular JavaScript File Uploader</a></span> — Resumable uploads from disk, Dropbox or GDrive, with wrappers for React, Vue, Svelte &amp; Angular. This version focuses on clean-up, with a rewritten S3 plugin and fewer packages to manage.</p>\n  <p>Transloadit </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189722/rss\" title=\"zod.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">Zod 4.5: Faster Parsing, 9x Less Memory Per Schema</a></span> — A big performance release for the popular TypeScript-first schema validation library. In a deep dive, Colin explains <a href=\"https://javascriptweekly.com/link/189723/rss\" style=\" color: #3366aa;   \">how method memoization helped cut</a> a bare <code>z.string()</code> from using 7.5KB of heap to just 784 bytes.</p>\n  <p>Colin McDonnell </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189695/rss\" title=\"try.expo.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">Know JavaScript? You're Ready to Ship a Mobile App</a></span> — Your React skills already ship mobile apps. Expo does the builds, store submission, and updates. No Xcode.</p>\n  <p>Expo <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189724/rss\" title=\"four.htmx.org\" style=\" color: #3366aa;    font-size: 1.05em;\">htmx 4.0: The Anti-SPA Library's First Major Release in Two Years</a></span> — The library that swaps HTML fragments into pages using nothing but attributes moves from <code>XMLHttpRequest</code> to the Fetch API. Attribute inheritance is now opt-in, events have been standardized/renamed, and <a href=\"https://javascriptweekly.com/link/189725/rss\" style=\" color: #3366aa;   \">morphing swaps</a> and <a href=\"https://javascriptweekly.com/link/189726/rss\" style=\" color: #3366aa;   \"><code>&lt;hx-partial&gt;</code></a> are new features. There's a full <a href=\"https://javascriptweekly.com/link/189727/rss\" style=\" color: #3366aa;   \">what's new in htmx 4 guide</a> too.</p>\n  <p>Carson Gross </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\"><p>💡 If you're new to htmx, <a href=\"https://javascriptweekly.com/link/189728/rss\" style=\" color: #3366aa; font-weight: 500;\">this page of patterns for common use cases</a> provides a great hands-on introduction.</p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/189696/rss\" title=\"sveltebits.xyz\" style=\" color: #3366aa;    font-size: 1.05em;\">Svelte Bits: Animated UI Components for Svelte</a></span> — A Svelte port of <a href=\"https://javascriptweekly.com/link/189697/rss\" style=\" color: #3366aa;   \">React Bits</a> (by the same creator), the popular suite of animation components for React.</p>\n  <p>David Haz </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<ul>\n<li>\n<p>🗓️ <a href=\"https://javascriptweekly.com/link/189700/rss\" style=\" color: #3366aa; font-weight: 500;   \">Schedule-X 4.7</a> – Large, schedule-style calendar control for React, Vue, Angular, Svelte and Preact apps.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/189680/rss\" style=\" color: #3366aa; font-weight: 500;   \">Mantine 9.6</a> – Popular, extensive React component suite. Adds a new media 'lightbox' component and more.</p>\n</li>\n<li>\n<p>🖼️ <a href=\"https://javascriptweekly.com/link/189701/rss\" style=\" color: #3366aa; font-weight: 500;   \">Cropper.js 2.2</a> – A mature image cropping control with <a href=\"https://javascriptweekly.com/link/189702/rss\" style=\" color: #3366aa; font-weight: 500;   \">a playground</a> where you can give it a spin.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/189703/rss\" style=\" color: #3366aa; font-weight: 500;   \">noble-curves 2.4</a> – Audited, minimal JS implementation of elliptic curve cryptography.</p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px;\">\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n\t<p>📰 Classifieds</p>\n  </td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p>⚡<a href=\"https://javascriptweekly.com/link/189705/rss\" style=\" color: #3366aa; font-weight: 500;   \">Zuplo</a> puts every API, AI, and MCP request behind one gateway. Route traffic, guard your MCP servers, and cap your AI costs. <a href=\"https://javascriptweekly.com/link/189705/rss\" style=\" color: #3366aa; font-weight: 500;   \">Try it free</a>.</p>\n \n<p>📄 Turn messy PDFs into structured JSON. <a href=\"https://javascriptweekly.com/link/189706/rss\" style=\" color: #3366aa; font-weight: 500;   \">See how Foxit’s Structural Extraction API</a> preserves tables, fields, and layout in four REST calls.</p>\n \n<p>Flaky tests slowing down dev? <a href=\"https://javascriptweekly.com/link/189707/rss\" style=\" color: #3366aa; font-weight: 500;   \">Meticulous</a> gives engineers confidence to ship faster by autonomously testing every edge case of your web app.</p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>📢  Elsewhere in the ecosystem</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<ul>\n<li>\n<p>🤖 Addy Osmani says <a href=\"https://javascriptweekly.com/link/189708/rss\" style=\" color: #3366aa; font-weight: 500;   \">mastery still comes from 'doing the reps'</a>, and that agents finishing tasks for you means building expertise has to be a more deliberate act.</p>\n</li>\n<li>\n<p>∑ <a href=\"https://javascriptweekly.com/link/189709/rss\" style=\" color: #3366aa; font-weight: 500;   \">Lean</a>, a language for writing machine-checked mathematical proofs, has a verifier that's \"just\" a type checker, so here's <a href=\"https://javascriptweekly.com/link/189710/rss\" style=\" color: #3366aa; font-weight: 500;   \">the concept demonstrated in TypeScript</a> instead!</p>\n</li>\n<li>\n<p>🚫 <a href=\"https://javascriptweekly.com/link/189711/rss\" style=\" color: #3366aa; font-weight: 500;   \">Google has removed all remaining Manifest V2 extensions from the Chrome Web Store</a>… uBlock Origin included.</p>\n</li>\n<li>\n<p>AWS Lambda has introduced <a href=\"https://javascriptweekly.com/link/189713/rss\" style=\" color: #3366aa; font-weight: 500;   \">a new Node.js 26 runtime in preview</a>.</p>\n</li>\n<li>\n<p>A detailed look at many of the things that <a href=\"https://javascriptweekly.com/link/189714/rss\" style=\" color: #3366aa; font-weight: 500;   \">should go in a modern HTML boilerplate</a>.</p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n</div>\n  </td></tr>\n</table>\n\n\n\n\n<img src=\"https://javascriptweekly.com/open/800/rss\" width=\"1\" height=\"1\" />","score":4},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"OpenClaw went viral. Meet the maintainers building and securing it.","link":"https://github.blog/open-source/maintainers/openclaw-went-viral-meet-the-maintainers-building-and-securing-it/","pubDate":"Thu, 27 Aug 2026 16:00:00 +0000","description":"<p>OpenClaw is the fastest-growing project in GitHub history. Peter Steinberger and several maintainers share what they learned in the project's first six months.</p>\n<p>The post <a href=\"https://github.blog/open-source/maintainers/openclaw-went-viral-meet-the-maintainers-building-and-securing-it/\">OpenClaw went viral. Meet the maintainers building and securing it.</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4}]}