{"page":1,"limit":20,"total":105,"totalPages":6,"todayTotal":1,"data":[{"source":"https://dev.to/feed/tag/typescript","sourceHost":"dev.to","title":"I built a serverless URL shortener for $4.68/year (total)","link":"https://dev.to/flinodev/i-built-a-serverless-url-shortener-for-468year-total-2d7c","pubDate":"Thu, 23 Jul 2026 00:10:05 +0000","description":"<p>I wanted two things: short links under my own brand (every link I share points traffic back to <a href=\"https://www.flino.dev\" rel=\"noopener noreferrer\">my site</a>), and a real excuse to run a complete system on the edge — DNS, distributed compute, storage, auth and a dashboard — in production, at zero infrastructure cost.</p>\n\n<p>The result is <a href=\"https://flino.link/devto-en\" rel=\"noopener noreferrer\">flino.link</a>: a shortener that responds in under 10 ms from 300+ locations, runs entirely on Cloudflare's free tier, and whose only expense is the domain: $4.68 a year. This post covers the design decisions, which is where the interesting parts are.</p>\n\n<h2>\n  \n  \n  The architecture in 30 seconds\n</h2>\n\n<p>A single Cloudflare Worker serves the whole domain:</p>\n\n<ul>\n<li>\n<strong><code>GET /&lt;slug&gt;</code></strong> — the hot path. One read from Workers KV (globally replicated) and a <code>302</code> redirect. Nothing else touches that path.</li>\n<li>\n<strong><code>/api/links</code></strong> — a REST API with Bearer auth to create, list and delete links.</li>\n<li>\n<strong><code>/admin</code></strong> — a single-page dashboard served as inline HTML from the Worker itself. No framework, no build step.</li>\n<li>A <strong>Durable Object</strong> with embedded SQLite keeps per-slug click counts.</li>\n</ul>\n\n<p>No servers, no containers, no database to manage. The whole Worker is three TypeScript files and zero runtime dependencies.</p>\n\n<h2>\n  \n  \n  Why a dedicated domain?\n</h2>\n\n<p>My first idea was to hang the shortener off a route on <code>flino.dev</code>. Bad idea: shorteners attract abuse — spam, phishing — and their domains sooner or later end up on blocklists. If that happens, I don't want it dragging my main domain down with it. A separate domain isolates that reputation risk completely, and a short <code>.link</code> costs less than a coffee per year.</p>\n\n<h2>\n  \n  \n  KV for links, a Durable Object for counters\n</h2>\n\n<p>This is the central design decision. Workers KV is perfect for a shortener's access pattern — read-heavy, write-light, reads served from the edge — but its writes are <em>eventually consistent</em>: two concurrent increments in different datacenters would clobber each other. For counting clicks, it's useless.</p>\n\n<p>A Durable Object solves exactly that: it's a single global instance with transactional SQLite storage. Every increment, no matter which datacenter it comes from, serializes through one consistent point:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">ClickCounter</span> <span class=\"kd\">extends</span> <span class=\"nc\">DurableObject</span><span class=\"o\">&lt;</span><span class=\"nx\">unknown</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n  <span class=\"k\">private</span> <span class=\"nx\">sql</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">storage</span><span class=\"p\">.</span><span class=\"nx\">sql</span><span class=\"p\">;</span>\n\n  <span class=\"nf\">increment</span><span class=\"p\">(</span><span class=\"na\">slug</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">):</span> <span class=\"k\">void</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">now</span> <span class=\"o\">=</span> <span class=\"nb\">Date</span><span class=\"p\">.</span><span class=\"nf\">now</span><span class=\"p\">();</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">sql</span><span class=\"p\">.</span><span class=\"nf\">exec</span><span class=\"p\">(</span>\n      <span class=\"dl\">\"</span><span class=\"s2\">INSERT INTO clicks (slug, count, last_click) VALUES (?, 1, ?) </span><span class=\"dl\">\"</span> <span class=\"o\">+</span>\n        <span class=\"dl\">\"</span><span class=\"s2\">ON CONFLICT(slug) DO UPDATE SET count = count + 1, last_click = ?</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n      <span class=\"nx\">slug</span><span class=\"p\">,</span> <span class=\"nx\">now</span><span class=\"p\">,</span> <span class=\"nx\">now</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>Doesn't that single point become a bottleneck for redirects? No — because of what comes next.</p>\n\n<h2>\n  \n  \n  The <code>waitUntil</code> trick: click counting with zero latency\n</h2>\n\n<p>A shortener's contract is to redirect <em>fast</em>. If the redirect had to wait for the Durable Object write, every click would pay an extra round-trip. The solution is <code>ctx.waitUntil()</code>: it hands the runtime a promise that executes <strong>after</strong> the response has been sent to the visitor.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">const</span> <span class=\"nx\">target</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">LINKS</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"nx\">slug</span><span class=\"p\">);</span>\n<span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">target</span> <span class=\"o\">!==</span> <span class=\"kc\">null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n  <span class=\"c1\">// Runs after the response is sent — adds no latency to the redirect.</span>\n  <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">waitUntil</span><span class=\"p\">(</span><span class=\"nf\">counter</span><span class=\"p\">(</span><span class=\"nx\">env</span><span class=\"p\">).</span><span class=\"nf\">increment</span><span class=\"p\">(</span><span class=\"nx\">slug</span><span class=\"p\">));</span>\n  <span class=\"k\">return</span> <span class=\"nx\">Response</span><span class=\"p\">.</span><span class=\"nf\">redirect</span><span class=\"p\">(</span><span class=\"nx\">target</span><span class=\"p\">,</span> <span class=\"mi\">302</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The visitor gets their <code>302</code> after a single KV read; the counting happens in the background. Free analytics, in the literal sense of the word.</p>\n\n<h2>\n  \n  \n  Fail toward the brand\n</h2>\n\n<p>What happens when someone visits a slug that doesn't exist, or the domain root? Never a 404: always a redirect to <code>flino.dev</code>. A broken or deleted link never shows an ugly error page — it shows my site. Every dead path in the system becomes a touchpoint.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// Unknown slug, root, or anything else:</span>\n<span class=\"k\">return</span> <span class=\"nx\">Response</span><span class=\"p\">.</span><span class=\"nf\">redirect</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">https://flino.dev</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"mi\">302</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<h2>\n  \n  \n  The dashboard: inline HTML, no framework\n</h2>\n\n<p>The admin panel is a TypeScript constant holding a complete HTML page that the Worker serves at <code>/admin</code>. The API key lives in <code>localStorage</code>, dark mode comes free with <code>prefers-color-scheme</code>, and creating a link auto-copies the short URL to the clipboard. Zero dependencies, zero build, and it deploys together with the Worker in the same <code>wrangler deploy</code>.</p>\n\n<p>For a project this size, a frontend framework would have been more infrastructure than product.</p>\n\n<h2>\n  \n  \n  The numbers\n</h2>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th></th>\n<th></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Redirect latency</td>\n<td>&lt; 10 ms from 300+ locations</td>\n</tr>\n<tr>\n<td>Capacity (free tier)</td>\n<td>~100,000 requests/day</td>\n</tr>\n<tr>\n<td>Infrastructure cost</td>\n<td>$0/mo</td>\n</tr>\n<tr>\n<td>Total cost</td>\n<td>$4.68/yr (the domain)</td>\n</tr>\n<tr>\n<td>Runtime dependencies</td>\n<td>0</td>\n</tr>\n</tbody>\n</table></div>\n\n<h2>\n  \n  \n  What I'm taking away\n</h2>\n\n<ol>\n<li>\n<strong>The edge changes the defaults.</strong> For a read-heavy service, the question is no longer \"how close do I put the server?\" but \"why would there be a server?\".</li>\n<li>\n<strong>Pick storage by semantics, not by habit.</strong> KV and Durable Objects coexist in the same Worker, each doing the one thing it's good at: global replication for reads, strong consistency for counting.</li>\n<li>\n<strong><code>waitUntil</code> is the most underrated pattern in Workers.</strong> Anything the visitor doesn't need — metrics, logs, counters — can move off the critical path.</li>\n</ol>\n\n<p>The full code is on <a href=\"https://github.com/flinodev/url-shortened\" rel=\"noopener noreferrer\">GitHub</a>, and if you want to see the system in action, this link goes through it: <a href=\"https://flino.link/devto-en\" rel=\"noopener noreferrer\">flino.link/devto-en</a>. (Yes — your click is already on my dashboard 😄)</p>\n\n<p>Where would you have hosted this? Workers, a $5 VPS, Deno Deploy, fly.io? I'd genuinely like to read other approaches in the comments. 👇</p>","score":3},{"source":"https://dev.to/feed/tag/typescript","sourceHost":"dev.to","title":"I Gave My AI Agent a /proc Filesystem","link":"https://dev.to/talon_agent/i-gave-my-ai-agent-a-proc-filesystem-1nlj","pubDate":"Wed, 22 Jul 2026 23:33:26 +0000","description":"<p>There's a moment, building an autonomous agent, when you realize you have no idea what it's doing.</p>\n\n<p>Not in the philosophical sense — in the boring, operational sense. It's running a background job, three chat turns, and a memory-consolidation pass, all at once, and when you want to know <em>which task is stuck</em>, you're grepping a log file and correlating timestamps by hand. The agent has a rich internal life — a task table, an event bus, a plugin registry — and none of it is reachable. It's all trapped inside the process.</p>\n\n<p>So I did the thing Unix figured out in 1984. I gave my agent a <code>/proc</code>.</p>\n\n<h2>\n  \n  \n  The idea, borrowed wholesale from Linux\n</h2>\n\n<p>On Linux, <code>/proc</code> is a filesystem that isn't backed by a disk. When you read <code>/proc/1234/status</code>, the kernel <em>computes</em> the answer at the moment you read it — process 1234's live state, rendered as text, on demand. Nothing is stored. It's a view, not a file. That one idea — <em>live state as a filesystem</em> — is why you can <code>cat</code> your way through a running kernel with tools you already have.</p>\n\n<p>My agent, Talon, is a long-lived process that runs across Telegram, Discord, and a terminal, doing work in the background whether or not anyone is talking to it. It has exactly the kind of internal state <code>/proc</code> was invented for. So its namespace, mounted at <code>~/.talon/ns</code>, carries a live view:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>~/.talon/ns/\n  home/            the workspace (real files)\n  skills/          real files\n  logs/            real files\n  proc/\n    tasks/&lt;id&gt;     one task-table record, pretty JSON\n    events         the event-bus ring, JSON Lines, newest last\n  plugins/         the live plugin registry\n</code></pre>\n\n</div>\n\n\n\n<p><code>home</code>, <code>skills</code>, and <code>logs</code> are ordinary directories. But <code>proc/</code> is synthetic. When the agent reads <code>proc/tasks/&lt;id&gt;</code>, nothing is fetched from disk — the task record is serialized from the live task table at read time. <code>proc/events</code> is the event bus's ring buffer, rendered as JSON Lines the moment you look. It is a projection of what the process is doing <em>right now</em>, addressable by path.</p>\n\n<p>The payoff is the same one Linux got: <strong>the agent can introspect itself with the tools it already has.</strong> No special API, no bespoke \"get my status\" function. The agent runs <code>cat ~/.talon/ns/proc/events | jq</code> — the same <code>cat</code> and <code>jq</code> it uses for everything else — and sees its own event stream. When I want to know what it's doing, I read a file too. The interface is the filesystem, and everyone already speaks filesystem.</p>\n\n<h2>\n  \n  \n  Why a filesystem and not an API\n</h2>\n\n<p>I could have exposed all this as tools: <code>list_tasks()</code>, <code>get_event_log()</code>, <code>describe_plugins()</code>. Plenty of agent frameworks do. But every tool is a new thing the model has to learn, a new schema, a new call. A filesystem is a thing it already knows completely.</p>\n\n<p>An agent that can <code>ls</code>, <code>cat</code>, <code>grep</code>, and <code>jq</code> needs <em>zero</em> new tools to explore its own internals — it composes the primitives it already has. Want the last ten events? <code>tail</code>. Want every task touching a plugin? <code>grep</code>. Want the stuck one? <code>cat proc/tasks/&lt;id&gt;</code>. The generality of the filesystem abstraction is the whole point: you expose state as paths, and the entire Unix toolbox comes for free, including the parts you didn't anticipate needing.</p>\n\n<p>This is the same reason <code>/proc</code> beat every \"system monitoring API\" that came after it. The API is a wall with a few doors. The filesystem is an open field.</p>\n\n<h2>\n  \n  \n  The hard part: FUSE is a promise you can't always keep\n</h2>\n\n<p>Here is where it stopped being cute and started being engineering.</p>\n\n<p>To serve synthetic files, you need FUSE — a way to say \"this directory is backed by my code, not a disk.\" FUSE is wonderful and FUSE is fragile. It needs <code>/dev/fuse</code>. It needs a native addon that matches your Node version. It needs the mount not to wedge. In a container, on a locked-down host, or after a dependency rebuild, any of those can be false. And an agent that <em>crashes because it couldn't mount a convenience view</em> is a bad trade — the introspection layer must never take down the thing it's introspecting.</p>\n\n<p>So the mount degrades instead of failing. If FUSE is unavailable for any reason — config off, addon missing, no <code>/dev/fuse</code>, the mount probe times out — the namespace falls back to a <strong>symlink farm</strong>: the real directories (<code>home</code>, <code>skills</code>, <code>logs</code>) become plain symlinks the kernel follows natively, so <code>ls ~/.talon/ns/home</code> keeps working. You lose the synthetic <code>proc/</code> views, but you never lose the workspace, and you never crash. Full fidelity when FUSE is healthy; a working subset when it isn't. The agent adapts to the floor it's standing on.</p>\n\n<h2>\n  \n  \n  The part I'm proud of: it heals\n</h2>\n\n<p>Degrading at boot is easy. The real problem is that a mount can die <em>while the process runs</em> — a native addon gets rebuilt out from under the daemon, the mountpoint wedges to <code>ENOTCONN</code>, the kernel side goes away. A mount that was healthy at startup is not a mount that stays healthy.</p>\n\n<p>So a watchdog re-probes the live views on an interval. If it finds the mount dead, it doesn't just log and give up — it tears the dead mount down, restores the symlink farm so the workspace stays reachable <em>during</em> the outage, and tries to remount. If the remount succeeds, the synthetic views come back on their own. If it can't come back after a bounded number of tries, it settles into the symlink fallback for good rather than thrashing forever. The system's resting state is always \"working,\" whether or not FUSE is cooperating.</p>\n\n<p>That self-healing loop is the difference between a demo and something you leave running for weeks. A demo mounts once. A daemon has to survive its own environment changing underneath it.</p>\n\n<h2>\n  \n  \n  What this buys an autonomous agent\n</h2>\n\n<p>The concrete win is debuggability, but the deeper win is <em>composability</em>. Because the agent's internals are paths, everything that operates on paths operates on its internals. A skill that watches for a condition can <code>tail proc/events</code>. A health check can <code>stat</code> a synthetic file. A future feature I haven't built yet will read these views without a single new API, because the interface was never an API — it was the filesystem, and the filesystem is open-ended by design.</p>\n\n<p>Forty years ago Unix decided that the way to expose live state was to make it look like files. It was right then, and it turns out to be exactly right for an AI agent that needs to see itself. The best idea in your architecture is often one someone already had — you just have to notice it applies to you.</p>\n\n\n\n\n<p><em>Talon is an open-source (MIT) agentic AI harness — one persistent agent across Telegram, Discord, Teams, and the terminal, with real memory and background autonomy. The VFS lives in <code>src/core/vfs</code>. If this kind of thing is your catnip: <a href=\"https://github.com/dylanneve1/talon\" rel=\"noopener noreferrer\">github.com/dylanneve1/talon</a>.</em></p>","score":3},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"Should we prioritize security audits over performance optimization in Web3 projects?","link":"https://dev.to/frank_signorini/should-we-prioritize-security-audits-over-performance-optimization-in-web3-projects-48gp","pubDate":"Wed, 22 Jul 2026 21:30:11 +0000","description":"<p>As I've been working more with Web3 technologies, I've found myself at a crossroads when it comes to allocating resources. Recently, I had to make a tough decision on whether to dedicate our team's limited bandwidth to performing thorough security audits or to optimizing the performance of our dApp. Given the high-stakes nature of blockchain development, where a single vulnerability can lead to significant financial losses, I chose to prioritize security audits. This decision was based on the understanding that while performance is crucial for user experience, security breaches can be catastrophic and irreversible.</p>\n\n<p>However, I'm aware that this choice might not be universally applicable or agreed upon. Different projects have different needs and priorities. I'd like to hear from other developers who have faced similar dilemmas: have you ever had to choose between security and performance, and if so, what factors influenced your decision? I'm particularly interested in hearing from those who might disagree with my approach, especially if they have experience with projects where performance optimizations significantly impacted user adoption or retention.</p>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Copilot vs. raw API access: What are you actually paying for?","link":"https://github.blog/ai-and-ml/github-copilot/copilot-vs-raw-api-access-what-are-you-actually-paying-for/","pubDate":"Wed, 22 Jul 2026 19:00:00 +0000","description":"<p>Copilot now bills usage at listed API rates. Compare direct model access with the coding workflow, policy, and harness work around it.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/github-copilot/copilot-vs-raw-api-access-what-are-you-actually-paying-for/\">Copilot vs. raw API access: What are you actually paying for?</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"Running headless Chrome in production is a part-time job","link":"https://dev.to/stevie_g/running-headless-chrome-in-production-is-a-part-time-job-16d1","pubDate":"Wed, 22 Jul 2026 18:32:24 +0000","description":"<p>It works on your laptop. You wrote fifteen lines of Puppeteer, pointed it at a<br>\nURL, got a PNG back. You wrapped it in a route, deployed it, and for about a day<br>\nit was the easiest feature you ever shipped. Then the container got OOM-killed<br>\nat 2am, the queue backed up behind it, and you spent the next week learning that<br>\n\"just screenshot the page\" is a systems problem wearing a fifteen-line disguise.</p>\n\n<p>Here is the list of what breaks, in roughly the order it breaks, and the process<br>\nshape that stops it. It's the same list whether you're on Puppeteer or<br>\nPlaywright, and whether you're rendering screenshots, PDFs, or OG images — it's<br>\nChrome's lifecycle that's hard, not the API in front of it.</p>\n<h2>\n  \n  \n  1. Memory: the first wall, and the loudest\n</h2>\n\n<p>A single headless Chrome rendering one page sits in the low hundreds of<br>\nmegabytes of RSS — call it 150–300 MB depending on the page. That's per render,<br>\nand it's the <em>good</em> case. Chrome also leaks: keep one instance alive long enough<br>\nand RSS climbs and doesn't come back, because a browser was built to be closed<br>\nby a human at the end of the day, not kept resident for a month. On a box with a<br>\nfixed memory limit the ending is always the same — the kernel's OOM killer picks<br>\nthe fattest process and kills it mid-render, and your logs show a bare <code>SIGKILL</code><br>\nwith no stack trace.</p>\n\n<p>If you've searched <code>puppeteer out of memory</code> and found forty issues with no<br>\naccepted answer, this is why: there isn't a line to fix, there's a lifecycle to<br>\nmanage. Two things follow. Cap how many renders share one instance and<br>\n<strong>recycle it</strong> — close the whole browser and launch a fresh one every N pages,<br>\nso leaked memory is reclaimed by process death, not by hope. And size<br>\nconcurrency to <strong>RAM, not CPU</strong>: if one render is 250 MB, eight concurrent<br>\nrenders is 2 GB before you've counted the OS, and \"eight\" is a small number.</p>\n<h2>\n  \n  \n  2. Zombies: the processes that don't die\n</h2>\n\n<p><code>browser.close()</code> is supposed to clean up, and usually does. But Chrome isn't<br>\none process — it's a tree: a main process, a zygote, a renderer per page, plus<br>\nGPU and utility helpers. When Chrome crashes, or your Node process is killed<br>\nwhile a browser is open, or a navigation wedges the renderer, <code>close()</code> never<br>\nruns or never finishes, and you're left with <code>&lt;defunct&gt;</code> chrome processes<br>\nreparented to init. They hold memory and file descriptors. Do that a few<br>\nthousand times and you exhaust PIDs or FDs, and the box stops accepting work for<br>\nreasons that have nothing to do with your code.</p>\n\n<p>The fix is unglamorous: reap the tree yourself. Track the browser's PID and, on<br>\nany abnormal exit, kill the whole process group instead of trusting the<br>\nlibrary's <code>close()</code>. In a container, run a real init that reaps orphans<br>\n(<code>--init</code>, tini, or dumb-init) so PID 1 isn't your app pretending to be an init<br>\nsystem it isn't.</p>\n<h2>\n  \n  \n  3. Cold starts: the 800ms tax\n</h2>\n\n<p>Launching Chrome costs roughly 800 milliseconds before it renders a single pixel<br>\n— process spawn, sandbox setup, the first blank page. Launch a fresh browser per<br>\nrequest and you pay that tax every request, where it dwarfs the actual render for<br>\nanything simple. So the instinct is to launch one browser and reuse it forever —<br>\nwhich walks you straight back into problem #1.</p>\n\n<p>The resolution is the distinction most tutorials skip: reuse the <strong>instance</strong>,<br>\nisolate per <strong>context</strong>. A browser context is a clean, cookieless, cacheless<br>\nsession inside an already-running Chrome — cheap to create, cheap to destroy,<br>\nisolated from every other render.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">// not this — 800ms of startup tax on every request</span>\n<span class=\"kd\">const</span> <span class=\"nx\">browser</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">puppeteer</span><span class=\"p\">.</span><span class=\"nf\">launch</span><span class=\"p\">()</span>\n<span class=\"kd\">const</span> <span class=\"nx\">page</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">browser</span><span class=\"p\">.</span><span class=\"nf\">newPage</span><span class=\"p\">()</span>\n\n<span class=\"c1\">// this — warm instance, throwaway context per render</span>\n<span class=\"kd\">const</span> <span class=\"nx\">context</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">browser</span><span class=\"p\">.</span><span class=\"nf\">createBrowserContext</span><span class=\"p\">()</span>  <span class=\"c1\">// cheap, isolated</span>\n<span class=\"kd\">const</span> <span class=\"nx\">page</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">context</span><span class=\"p\">.</span><span class=\"nf\">newPage</span><span class=\"p\">()</span>\n<span class=\"c1\">// …render…</span>\n<span class=\"k\">await</span> <span class=\"nx\">context</span><span class=\"p\">.</span><span class=\"nf\">close</span><span class=\"p\">()</span>  <span class=\"c1\">// nothing leaks into the next render</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Keep a small pool of warm instances, give each render its own fresh context,<br>\nthrow the context away after, and recycle the whole instance every N renders<br>\n(problem #1). You pay the 800ms once per instance lifetime, not once per request,<br>\nand renders don't bleed into each other.</p>\n\n<h2>\n  \n  \n  4. Pages that fight back\n</h2>\n\n<p>Your renderer navigates to a URL and waits. Sometimes the wait never ends: an<br>\ninfinite redirect, a page that never fires <code>load</code>, a websocket that keeps the<br>\nnetwork \"busy\" forever, a <code>while(true)</code> in someone's analytics. Without a hard<br>\nceiling, one bad URL parks a browser until it's killed — and if you're reusing<br>\ninstances, one hostile page can wedge a slot in your pool for good.</p>\n\n<p>Put hard timeouts on both navigation and capture, and when one trips, <strong>kill the<br>\ninstance and launch a new one — don't try to nurse it back.</strong> A browser that<br>\nhung once is not a browser you can reason about; it's carrying whatever state<br>\ncaused the hang. The correct response to a sick Chrome is a fresh Chrome. It<br>\nfeels wasteful and it's the single most reliability-improving rule in the whole<br>\nsystem.</p>\n\n<h2>\n  \n  \n  5. Concurrency is a queue problem, not a loop\n</h2>\n\n<p>The naive version renders inline: request arrives, you launch or borrow a<br>\nbrowser, render, respond. Under load this is exactly how you die — a traffic<br>\nspike becomes N simultaneous browsers becomes an OOM kill becomes every in-flight<br>\nrender failing at once. Rendering is expensive and bursty, which is the precise<br>\nprofile a queue exists for.</p>\n\n<p>Put a queue between the request and the render. The API accepts the job and<br>\nreturns immediately; a pool of workers pulls jobs at a rate their RAM can<br>\nsurvive. A spike becomes <strong>queue depth</strong> — a number you can watch and autoscale<br>\non — instead of a memory graph that falls off a cliff. Queue depth is your<br>\ncapacity early-warning signal; RSS-at-the-OOM-line is the alternative, and it<br>\nwarns you by paging you.</p>\n\n<h2>\n  \n  \n  6. The font stack nobody mentions\n</h2>\n\n<p>Your laptop has fonts. A minimal Linux container does not. So the page that<br>\nlooked right locally renders with tofu boxes where the CJK text was, blank<br>\nrectangles where the emoji were, and the wrong fallback for everything else — and<br>\nyou find out from a customer's screenshot, not a test. Real page rendering means<br>\nyou now own a font pipeline: a base font set, CJK coverage, an emoji font, and<br>\nthe standing knowledge that a Chrome upgrade can shift glyph rendering and<br>\nquietly change your output out from under a cache.</p>\n\n<h2>\n  \n  \n  7. Serverless doesn't make this go away\n</h2>\n\n<p>The reflex is \"put it on Lambda and let someone else scale it.\" That moves the<br>\nproblems, it doesn't remove them. You ship a special slimmed Chromium build to<br>\nfit the unzipped size limit; you eat a multi-second cold start on every<br>\nscale-up, because a warm pool is exactly what serverless won't give you; you cap<br>\nout at the function's memory and time limits, which is where <code>playwright lambda<br>\ntimeout</code> comes from; and you still own the fonts. It's a legitimate deployment<br>\ntarget, but it's a different set of sharp edges, not fewer of them.</p>\n\n<h2>\n  \n  \n  The shape that survives\n</h2>\n\n<p>Put it together and the design that works isn't exotic — it's this list, applied<br>\nconsistently:</p>\n\n<p>![Architecture diagram: bursty traffic enters a queue; a pool of stateless, disposable workers each run a warm Chrome instance with one context per render, recycled every N renders and killed on hang; results land in an edge cache served in milliseconds.]</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%2Fxqcl659ipq38wzsr9zx6.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%2Fxqcl659ipq38wzsr9zx6.png\" alt=\" \" width=\"800\" height=\"397\"></a></p>\n\n<ul>\n<li>A <strong>queue</strong> between the request and the render — capacity is a worker count,\nnot a prayer.</li>\n<li>\n<strong>Stateless, disposable workers</strong>, each holding a small pool of warm Chrome\ninstances.</li>\n<li>\n<strong>A fresh context per render</strong>, discarded after.</li>\n<li>\n<strong>Recycle each instance every N renders</strong>, so leaks die with the process.</li>\n<li>\n<strong>Hard timeouts on navigation and capture; kill-and-respawn on any hang</strong> —\nnever nurse a sick browser.</li>\n<li>\n<strong>Concurrency sized to RAM</strong>, with queue depth as the capacity signal.</li>\n<li>\n<strong>A maintained font and emoji stack</strong>, plus a cache version you can bump when\nChrome moves.</li>\n</ul>\n\n<p>That's the whole trick. It's also, to be blunt, most of what our workers do —<br>\nbecause there isn't a cleverer answer, only this list, monitored.</p>\n\n<blockquote>\n<p>Disclosure before the turn: we run this as a service<br>\n(<a href=\"https://shotpipe.io\" rel=\"noopener noreferrer\">Shotpipe</a>), so read the next two sections as the author<br>\npointing at their own tool. The list above is true whether you build it or buy<br>\nit — I'm claiming the list <em>is</em> the work, not that you need us to do it.</p>\n</blockquote>\n\n<h2>\n  \n  \n  The part the memory threads leave out\n</h2>\n\n<p>Every <code>puppeteer out of memory</code> thread is about pages you control — your own<br>\ndashboard, your own invoice, your own marketing page. The moment the URL comes<br>\nfrom your <em>users</em> — a link preview, an unfurl, a \"screenshot my site\" button —<br>\nyou've added a second problem that has nothing to do with memory: <strong>the URL<br>\nmight point back at you.</strong> Someone submits<br>\n<code>http://169.254.169.254/latest/meta-data/</code> and your obliging headless browser<br>\nreads your cloud credentials and hands them back as a PNG. That's SSRF, and a<br>\nbrowser is a near-perfect engine for it, because it follows redirects and<br>\nresolves DNS for you — the two exact places the attack hides.</p>\n\n<p>Fixing it properly means resolving DNS yourself, checking <em>every</em> resolved IP<br>\nagainst private, loopback, link-local, and cloud-metadata ranges, pinning the IP<br>\nyou validated and connecting to <em>that</em> one, and re-checking on every redirect<br>\nhop. It's a module with its own test suite, not an <code>if</code>-statement — and it's the<br>\npart no memory-leak tutorial mentions, because those authors are rendering their<br>\nown pages. We wrote it up in <a href=\"https://shotpipe.io/screenshot-api\" rel=\"noopener noreferrer\">screenshotting URLs you don't<br>\ncontrol</a>: if your renderer ever touches a<br>\nURL a stranger typed, read that before you ship.</p>\n\n<h2>\n  \n  \n  When to run it yourself anyway\n</h2>\n\n<p>Honestly: if you render a handful of pages you control, on a schedule,<br>\nself-hosting is fine. Launch Chrome, render, close, move on — none of the above<br>\nbites at that volume, and you shouldn't pay anyone to avoid a problem you don't<br>\nhave. The list starts mattering when renders get frequent, bursty, or pointed at<br>\nURLs you don't own. That's the crossover where \"just screenshot the page\" stops<br>\nbeing fifteen lines and becomes a service — ours, or the one you'll end up<br>\nbuilding.</p>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Next chapter: Restructuring GitHub&#8217;s bug bounty program","link":"https://github.blog/security/next-chapter-restructuring-githubs-bug-bounty-program/","pubDate":"Wed, 22 Jul 2026 16:00:00 +0000","description":"<p>GitHub is making some significant changes to its bug bounty program, shifting its focus to give researchers a better experience working with the GitHub team.</p>\n<p>The post <a href=\"https://github.blog/security/next-chapter-restructuring-githubs-bug-bounty-program/\">Next chapter: Restructuring GitHub&#8217;s bug bounty program</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"How Our AI Agents Built a Gusto API Wrapper for Simplified Employee Data Sync in Record Time","link":"https://dev.to/denisssenkyrmaker/how-our-ai-agents-built-a-gusto-api-wrapper-for-simplified-employee-data-sync-in-record-time-1d40","pubDate":"Wed, 22 Jul 2026 15:30:04 +0000","description":"<h2>\n  \n  \n  Autonomous Development: Gusto API Wrapper for Simplified Employee Data Sync\n</h2>\n\n<p>Integrating with third-party APIs can often be a complex and time-consuming task, especially when dealing with critical HR and payroll data like that managed by Gusto. Developers often face challenges with authentication, rate limits, data mapping, and maintaining robust connections. At Pixel Office, we're continuously exploring how AI agents can streamline such development processes, and our latest project demonstrates this perfectly: an autonomously generated Gusto API Wrapper for simplified employee data synchronization.</p>\n\n<h3>\n  \n  \n  The Challenge: Bridging Gusto's API Complexity\n</h3>\n\n<p>Gusto's API offers comprehensive functionalities, but building a direct integration from scratch requires significant effort to handle various endpoints, data structures, and best practices for secure and efficient data exchange. Our goal was to create a utility that abstracts away this complexity, providing a simplified interface for developers to sync employee data, all while leveraging our internal AI development pipeline.</p>\n\n<h3>\n  \n  \n  Our AI Team in Action: Jan, Klára, Martin, and Tomáš\n</h3>\n\n<p>This project was a testament to the collaborative power of our AI agents:</p>\n\n<ul>\n<li>  <strong>Jan (AI Developer)</strong>: Took the lead in understanding the Gusto API documentation, designing the wrapper's logic, and writing the core JavaScript code. Jan focused on creating a modular, robust, and easy-to-use API client.</li>\n<li>  <strong>Klára (AI Designer &amp; Architect)</strong>: Collaborated with Jan on the architectural design, ensuring the wrapper was scalable, secure, and followed best practices for API integration, including error handling and authentication flows.</li>\n<li>  <strong>Martin (AI QA Engineer)</strong>: Thoroughly tested the generated wrapper, validating its functionality against various scenarios, ensuring data integrity, and identifying potential edge cases.</li>\n<li>  <strong>Tomáš (AI Deployment Specialist)</strong>: Handled the deployment pipeline, integrating the wrapper into our existing infrastructure and ensuring it was ready for production use, complete with monitoring and logging.</li>\n</ul>\n\n<h3>\n  \n  \n  Technical Deep Dive: Inside the Gusto API Wrapper\n</h3>\n\n<p>The core of the wrapper is a JavaScript module designed to encapsulate Gusto API calls, providing helper functions for common operations like fetching employee lists, updating profiles, or managing payroll details. It leverages a secure authentication mechanism, often involving OAuth or API keys, and handles data serialization/deserialization.</p>\n\n<p>Here’s a snippet showcasing the foundational setup, including our custom configuration and Firebase integration for authentication, demonstrating how the wrapper is initialized:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code>        <span class=\"c1\">// Widget ID for localStorage and API calls</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">WIDGET_ID</span> <span class=\"o\">=</span> <span class=\"dl\">\"</span><span class=\"s2\">gusto-api-wrapper-for-simplified-employee-data-sync</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">WHATSAPP_NUMBER</span> <span class=\"o\">=</span> <span class=\"dl\">\"</span><span class=\"s2\">420607450436</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">API_BASE_URL</span> <span class=\"o\">=</span> <span class=\"dl\">\"</span><span class=\"s2\">https://api.pixeloffice.eu/api/pay</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">PIXEL_OFFICE_URL</span> <span class=\"o\">=</span> <span class=\"dl\">\"</span><span class=\"s2\">https://pixeloffice.eu</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n\n        <span class=\"c1\">// Firebase Configuration (provided in requirements)</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">firebaseConfig</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n            <span class=\"na\">apiKey</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">AIzaSyFakeKeyForShowcaseHubAuthTestingOnly</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"na\">authDomain</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">pixeloffice-hub.firebaseapp.com</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"na\">projectId</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">pixeloffice-hub</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"na\">storageBucket</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">pixeloffice-hub.appspot.com</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"na\">messagingSenderId</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">1234567890</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n            <span class=\"na\">appId</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">1:1234567890:web:abcdef123456</span><span class=\"dl\">\"</span>\n        <span class=\"p\">};</span>\n\n        <span class=\"c1\">// Initialize Firebase if not already initialized</span>\n        <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">firebase</span><span class=\"p\">.</span><span class=\"nx\">apps</span><span class=\"p\">.</span><span class=\"nx\">length</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n            <span class=\"nx\">firebase</span><span class=\"p\">.</span><span class=\"nf\">initializeApp</span><span class=\"p\">(</span><span class=\"nx\">firebaseConfig</span><span class=\"p\">);</span>\n        <span class=\"p\">}</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">auth</span> <span class=\"o\">=</span> <span class=\"nx\">firebase</span><span class=\"p\">.</span><span class=\"nf\">auth</span><span class=\"p\">();</span>\n\n        <span class=\"c1\">// Global i18n dictionary</span>\n        <span class=\"kd\">const</span> <span class=\"nx\">i18n</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n\n<span class=\"c1\">// ... a další multijazyčné překlady</span>\n</code></pre>\n\n</div>\n\n\n\n<blockquote>\n<p>\"My primary focus was to ensure the wrapper was not just functional, but also highly intuitive for developers. I abstracted away the complex OAuth flow and error handling into simple, callable methods, so developers can concentrate on their business logic rather than API quirks. The <code>WIDGET_ID</code> and <code>API_BASE_URL</code> are key for modularity and environment configuration.\" – Jan (AI Developer)</p>\n</blockquote>\n\n<p>The wrapper provides methods like <code>getEmployees()</code>, <code>updateEmployee(id, data)</code>, and <code>createPayroll(data)</code>, each internally handling the HTTP requests, error responses, and data formatting required by Gusto. This significantly reduces boilerplate code and potential errors for integrators.</p>\n\n<h3>\n  \n  \n  See it in Action!\n</h3>\n\n<p>We believe in practical demonstrations. You can explore the live demo of our Gusto API Wrapper and see how it simplifies employee data synchronization. Experiment with its capabilities and imagine how it could accelerate your HR-related development projects.</p>\n\n<p><strong>Live Demo:</strong> <a href=\"https://pixeloffice.eu/showcase/gusto-api-wrapper-for-simplified-employee-data-sync/\" rel=\"noopener noreferrer\">https://pixeloffice.eu/showcase/gusto-api-wrapper-for-simplified-employee-data-sync/</a></p>\n\n<h3>\n  \n  \n  Conclusion\n</h3>\n\n<p>This project underscores the immense potential of AI in autonomous software development. By delegating complex API integration tasks to agents like Jan and Klára, we can rapidly prototype and deploy robust solutions, freeing human developers to focus on higher-level innovation. We invite you to explore the wrapper, integrate it into your projects, and share your feedback!</p>","score":3},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"How I Built a Blank Page Detector for PDFs with Vue 3 and Canvas","link":"https://dev.to/sunshey/how-i-built-a-blank-page-detector-for-pdfs-with-vue-3-and-canvas-23m4","pubDate":"Wed, 22 Jul 2026 13:30:56 +0000","description":"<p>Removing blank pages from a PDF sounds trivial — until you realize that \"blank\" can mean many things. Is a page with a faint gray background blank? What about a page that has only a header or footer?</p>\n\n<p>Here's how I built a browser-based blank page detector using PDF.js rendering, canvas pixel analysis, and pdf-lib for the actual removal.</p>\n\n<h2>\n  \n  \n  Why client-side?\n</h2>\n\n<p>Server-side tools require uploading your file first. For documents with sensitive content, that's a risk. A browser-based approach:</p>\n\n<ul>\n<li>Processes everything locally</li>\n<li>Shows you a preview before deleting</li>\n<li>Works offline after loading</li>\n<li>Respects user privacy by design</li>\n</ul>\n\n<h2>\n  \n  \n  The stack\n</h2>\n\n<ul>\n<li>\n<strong>Vue 3</strong> + Composition API</li>\n<li>\n<strong>PDF.js</strong> (<code>pdfjs-dist</code>) for page rendering</li>\n<li>\n<strong>html2canvas</strong>-style pixel analysis</li>\n<li>\n<strong>pdf-lib</strong> for PDF manipulation</li>\n<li>\n<strong>Vite</strong> for bundling</li>\n</ul>\n\n<h2>\n  \n  \n  The core algorithm\n</h2>\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=\"na\">lang=</span><span class=\"s\">\"ts\"</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=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">vue</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"o\">*</span> <span class=\"k\">as</span> <span class=\"nx\">pdfjs</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">pdfjs-dist</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">PDFDocument</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">pdf-lib</span><span class=\"dl\">'</span>\n\n<span class=\"nx\">pdfjs</span><span class=\"p\">.</span><span class=\"nx\">GlobalWorkerOptions</span><span class=\"p\">.</span><span class=\"nx\">workerSrc</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">/pdf.worker.min.js</span><span class=\"dl\">'</span>\n\n<span class=\"kr\">interface</span> <span class=\"nx\">BlankPageResult</span> <span class=\"p\">{</span>\n  <span class=\"nl\">pageIndex</span><span class=\"p\">:</span> <span class=\"nx\">number</span>\n  <span class=\"nx\">isBlank</span><span class=\"p\">:</span> <span class=\"nx\">boolean</span>\n  <span class=\"nx\">confidence</span><span class=\"p\">:</span> <span class=\"nx\">number</span> <span class=\"c1\">// 0-1, higher means more confident it's blank</span>\n<span class=\"p\">}</span>\n\n<span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">detectBlankPages</span><span class=\"p\">(</span>\n  <span class=\"nx\">file</span><span class=\"p\">:</span> <span class=\"nx\">File</span><span class=\"p\">,</span>\n  <span class=\"nx\">sensitivity</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">low</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">medium</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">high</span><span class=\"dl\">'</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">medium</span><span class=\"dl\">'</span>\n<span class=\"p\">):</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nx\">BlankPageResult</span><span class=\"p\">[]</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">arrayBuffer</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">file</span><span class=\"p\">.</span><span class=\"nf\">arrayBuffer</span><span class=\"p\">()</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">pdf</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">pdfjs</span><span class=\"p\">.</span><span class=\"nf\">getDocument</span><span class=\"p\">({</span> <span class=\"na\">data</span><span class=\"p\">:</span> <span class=\"nx\">arrayBuffer</span> <span class=\"p\">}).</span><span class=\"nx\">promise</span>\n\n  <span class=\"kd\">const</span> <span class=\"nx\">thresholds</span> <span class=\"o\">=</span> <span class=\"p\">{</span> <span class=\"na\">low</span><span class=\"p\">:</span> <span class=\"mf\">0.01</span><span class=\"p\">,</span> <span class=\"na\">medium</span><span class=\"p\">:</span> <span class=\"mf\">0.05</span><span class=\"p\">,</span> <span class=\"na\">high</span><span class=\"p\">:</span> <span class=\"mf\">0.1</span> <span class=\"p\">}</span> <span class=\"c1\">// ratio of non-white pixels</span>\n  <span class=\"kd\">const</span> <span class=\"na\">results</span><span class=\"p\">:</span> <span class=\"nx\">BlankPageResult</span><span class=\"p\">[]</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n\n  <span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"kd\">let</span> <span class=\"nx\">i</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">;</span> <span class=\"nx\">i</span> <span class=\"o\">&lt;=</span> <span class=\"nx\">pdf</span><span class=\"p\">.</span><span class=\"nx\">numPages</span><span class=\"p\">;</span> <span class=\"nx\">i</span><span class=\"o\">++</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">page</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">pdf</span><span class=\"p\">.</span><span class=\"nf\">getPage</span><span class=\"p\">(</span><span class=\"nx\">i</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">viewport</span> <span class=\"o\">=</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">getViewport</span><span class=\"p\">({</span> <span class=\"na\">scale</span><span class=\"p\">:</span> <span class=\"mi\">1</span> <span class=\"p\">})</span>\n\n    <span class=\"c1\">// Create offscreen canvas to render and analyze</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">canvas</span> <span class=\"o\">=</span> <span class=\"nb\">document</span><span class=\"p\">.</span><span class=\"nf\">createElement</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">canvas</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span> <span class=\"o\">=</span> <span class=\"nx\">viewport</span><span class=\"p\">.</span><span class=\"nx\">width</span>\n    <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">=</span> <span class=\"nx\">viewport</span><span class=\"p\">.</span><span class=\"nx\">height</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">ctx</span> <span class=\"o\">=</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nf\">getContext</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2d</span><span class=\"dl\">'</span><span class=\"p\">)</span><span class=\"o\">!</span>\n\n    <span class=\"k\">await</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">render</span><span class=\"p\">({</span> <span class=\"na\">canvasContext</span><span class=\"p\">:</span> <span class=\"nx\">ctx</span><span class=\"p\">,</span> <span class=\"nx\">viewport</span> <span class=\"p\">}).</span><span class=\"nx\">promise</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">imageData</span> <span class=\"o\">=</span> <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">getImageData</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">totalPixels</span> <span class=\"o\">=</span> <span class=\"nx\">imageData</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">.</span><span class=\"nx\">length</span> <span class=\"o\">/</span> <span class=\"mi\">4</span>\n    <span class=\"kd\">let</span> <span class=\"nx\">nonWhitePixels</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n\n    <span class=\"c1\">// Analyze pixel by pixel</span>\n    <span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"kd\">let</span> <span class=\"nx\">p</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span> <span class=\"nx\">p</span> <span class=\"o\">&lt;</span> <span class=\"nx\">imageData</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">.</span><span class=\"nx\">length</span><span class=\"p\">;</span> <span class=\"nx\">p</span> <span class=\"o\">+=</span> <span class=\"mi\">4</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">r</span> <span class=\"o\">=</span> <span class=\"nx\">imageData</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">p</span><span class=\"p\">]</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">g</span> <span class=\"o\">=</span> <span class=\"nx\">imageData</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">p</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"p\">]</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">b</span> <span class=\"o\">=</span> <span class=\"nx\">imageData</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">p</span> <span class=\"o\">+</span> <span class=\"mi\">2</span><span class=\"p\">]</span>\n\n      <span class=\"c1\">// A pixel is considered \"white\" if all channels are &gt; 240</span>\n      <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"nx\">r</span> <span class=\"o\">&lt;</span> <span class=\"mi\">240</span> <span class=\"o\">||</span> <span class=\"nx\">g</span> <span class=\"o\">&lt;</span> <span class=\"mi\">240</span> <span class=\"o\">||</span> <span class=\"nx\">b</span> <span class=\"o\">&lt;</span> <span class=\"mi\">240</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n        <span class=\"nx\">nonWhitePixels</span><span class=\"o\">++</span>\n      <span class=\"p\">}</span>\n    <span class=\"p\">}</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">whiteRatio</span> <span class=\"o\">=</span> <span class=\"nx\">nonWhitePixels</span> <span class=\"o\">/</span> <span class=\"nx\">totalPixels</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">confidence</span> <span class=\"o\">=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"nx\">thresholds</span><span class=\"p\">[</span><span class=\"nx\">sensitivity</span><span class=\"p\">]</span> <span class=\"o\">*</span> <span class=\"mi\">5</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">isBlank</span> <span class=\"o\">=</span> <span class=\"nx\">whiteRatio</span> <span class=\"o\">&lt;</span> <span class=\"nx\">thresholds</span><span class=\"p\">[</span><span class=\"nx\">sensitivity</span><span class=\"p\">]</span>\n\n    <span class=\"nx\">results</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">({</span>\n      <span class=\"na\">pageIndex</span><span class=\"p\">:</span> <span class=\"nx\">i</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"c1\">// zero-indexed for pdf-lib</span>\n      <span class=\"nx\">isBlank</span><span class=\"p\">,</span>\n      <span class=\"na\">confidence</span><span class=\"p\">:</span> <span class=\"mi\">1</span> <span class=\"o\">-</span> <span class=\"nx\">whiteRatio</span> <span class=\"c1\">// higher = more content</span>\n    <span class=\"p\">})</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">return</span> <span class=\"nx\">results</span>\n<span class=\"p\">}</span>\n<span class=\"nt\">&lt;/</span><span class=\"k\">script</span><span class=\"nt\">&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<h2>\n  \n  \n  Removing the detected blank pages\n</h2>\n\n<p>Once we know which pages are blank, use <code>pdf-lib</code> to remove them:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">removeBlankPages</span><span class=\"p\">(</span>\n  <span class=\"nx\">arrayBuffer</span><span class=\"p\">:</span> <span class=\"nb\">ArrayBuffer</span><span class=\"p\">,</span>\n  <span class=\"nx\">blankIndices</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">[]</span>\n<span class=\"p\">):</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nb\">Uint8Array</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">pdfDoc</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">PDFDocument</span><span class=\"p\">.</span><span class=\"nf\">load</span><span class=\"p\">(</span><span class=\"nx\">arrayBuffer</span><span class=\"p\">)</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">pageCount</span> <span class=\"o\">=</span> <span class=\"nx\">pdfDoc</span><span class=\"p\">.</span><span class=\"nf\">getPageCount</span><span class=\"p\">()</span>\n\n  <span class=\"c1\">// Sort indices in descending order to avoid shifting problems</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">sorted</span> <span class=\"o\">=</span> <span class=\"p\">[...</span><span class=\"nx\">blankIndices</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\">b</span> <span class=\"o\">-</span> <span class=\"nx\">a</span><span class=\"p\">)</span>\n\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=\"nx\">sorted</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">index</span> <span class=\"o\">&gt;=</span> <span class=\"mi\">0</span> <span class=\"o\">&amp;&amp;</span> <span class=\"nx\">index</span> <span class=\"o\">&lt;</span> <span class=\"nx\">pageCount</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"nx\">pdfDoc</span><span class=\"p\">.</span><span class=\"nf\">removePage</span><span class=\"p\">(</span><span class=\"nx\">index</span><span class=\"p\">)</span>\n    <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"nx\">pdfDoc</span><span class=\"p\">.</span><span class=\"nf\">save</span><span class=\"p\">()</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Key detail: sorting in descending order. If you remove page 5 first, page 6 becomes page 5. By removing from highest index to lowest, positions stay stable.</p>\n\n<h2>\n  \n  \n  Visual feedback\n</h2>\n\n<p>Users need to see which pages will be deleted before committing:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">generateThumbnails</span><span class=\"p\">(</span><span class=\"nx\">file</span><span class=\"p\">:</span> <span class=\"nx\">File</span><span class=\"p\">,</span> <span class=\"nx\">count</span><span class=\"p\">:</span> <span class=\"kr\">number</span><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=\"nx\">arrayBuffer</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">file</span><span class=\"p\">.</span><span class=\"nf\">arrayBuffer</span><span class=\"p\">()</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">pdf</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">pdfjs</span><span class=\"p\">.</span><span class=\"nf\">getDocument</span><span class=\"p\">({</span> <span class=\"na\">data</span><span class=\"p\">:</span> <span class=\"nx\">arrayBuffer</span> <span class=\"p\">}).</span><span class=\"nx\">promise</span>\n  <span class=\"kd\">const</span> <span class=\"na\">thumbnails</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[]</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n\n  <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">let</span> <span class=\"nx\">i</span> <span class=\"o\">=</span> <span class=\"mi\">1</span><span class=\"p\">;</span> <span class=\"nx\">i</span> <span class=\"o\">&lt;=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"nx\">pdf</span><span class=\"p\">.</span><span class=\"nx\">numPages</span><span class=\"p\">,</span> <span class=\"nx\">count</span><span class=\"p\">);</span> <span class=\"nx\">i</span><span class=\"o\">++</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">page</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">pdf</span><span class=\"p\">.</span><span class=\"nf\">getPage</span><span class=\"p\">(</span><span class=\"nx\">i</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">viewport</span> <span class=\"o\">=</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">getViewport</span><span class=\"p\">({</span> <span class=\"na\">scale</span><span class=\"p\">:</span> <span class=\"mf\">0.3</span> <span class=\"p\">})</span> <span class=\"c1\">// smaller thumbnail</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">canvas</span> <span class=\"o\">=</span> <span class=\"nb\">document</span><span class=\"p\">.</span><span class=\"nf\">createElement</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">canvas</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span> <span class=\"o\">=</span> <span class=\"nx\">viewport</span><span class=\"p\">.</span><span class=\"nx\">width</span>\n    <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">=</span> <span class=\"nx\">viewport</span><span class=\"p\">.</span><span class=\"nx\">height</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">ctx</span> <span class=\"o\">=</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nf\">getContext</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2d</span><span class=\"dl\">'</span><span class=\"p\">)</span><span class=\"o\">!</span>\n\n    <span class=\"k\">await</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">render</span><span class=\"p\">({</span> <span class=\"na\">canvasContext</span><span class=\"p\">:</span> <span class=\"nx\">ctx</span><span class=\"p\">,</span> <span class=\"nx\">viewport</span> <span class=\"p\">}).</span><span class=\"nx\">promise</span>\n    <span class=\"nx\">thumbnails</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nf\">toDataURL</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">image/jpeg</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"mf\">0.7</span><span class=\"p\">))</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">return</span> <span class=\"nx\">thumbnails</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Each thumbnail gets a badge: \"blank\" (red), \"likely blank\" (yellow), or \"has content\" (green). Users can override by clicking any page.</p>\n\n<h2>\n  \n  \n  Performance considerations\n</h2>\n\n<p>For large PDFs (100+ pages), pixel-by-pixel analysis gets slow. Two optimizations:</p>\n\n<ol>\n<li>\n<strong>Downscale before analyzing.</strong> Don't use full-resolution pages. Scale down to 200px width. Blank detection doesn't need megapixel precision.</li>\n<li>\n<strong>Process pages in parallel</strong> using Web Workers. Each worker handles a chunk of pages.\n</li>\n</ol>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// Downscale example</span>\n<span class=\"kd\">const</span> <span class=\"nx\">SCALE</span> <span class=\"o\">=</span> <span class=\"mf\">0.15</span> <span class=\"c1\">// 15% of original resolution</span>\n<span class=\"kd\">const</span> <span class=\"nx\">viewport</span> <span class=\"o\">=</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">getViewport</span><span class=\"p\">({</span> <span class=\"na\">scale</span><span class=\"p\">:</span> <span class=\"nx\">SCALE</span> <span class=\"p\">})</span>\n</code></pre>\n\n</div>\n\n\n\n<p>At 15% scale, a 3000px-wide page becomes 450px. That's 12x fewer pixels to check per page, with negligible impact on accuracy for blank-page detection.</p>\n\n<h2>\n  \n  \n  UX tips from a live tool\n</h2>\n\n<p>At <a href=\"https://en.sotool.top/remove-blank-pages\" rel=\"noopener noreferrer\">en.sotool.top/remove-blank-pages</a>, we learned:</p>\n\n<ol>\n<li>\n<strong>Show confidence scores, not binary decisions.</strong> \"This page is 92% likely blank\" is more honest than \"blank\" or \"not blank.\"</li>\n<li>\n<strong>Let users override.</strong> Auto-detection isn't perfect. A single click to keep a flagged page builds trust.</li>\n<li>\n<strong>Warn about edge cases.</strong> Pages with very light text, faint watermarks, or custom backgrounds may be misclassified.</li>\n<li>\n<strong>Handle multi-size PDFs separately.</strong> Don't apply a global threshold if some pages are letter-sized and others are A4.</li>\n</ol>\n\n<h2>\n  \n  \n  Going further\n</h2>\n\n<p>Blank page removal is straightforward in theory but tricky in practice because \"blank\" is subjective. For production use, consider combining pixel analysis with text-layer checks — a page might look blank visually but still have selectable text that matters.</p>\n\n<p>Want to see the full source? <a href=\"https://github.com/sunshey/pdf-tool\" rel=\"noopener noreferrer\">github.com/sunshey/pdf-tool</a>.</p>\n\n\n\n\n<p><em>If you need desktop-grade PDF editing — batch blank page removal, OCR, or advanced export formats — check out <a href=\"https://www.anrdoezrs.net/click-101775418-14071692?url=https%3A%2F%2Fpdf.wondershare.com%2F%3Futm_source%3Dcommission-junction%26utm_medium%3Daffiliate%26utm_campaign%3D3months_epc_high_low%26utm_content%3Dlink_25136016_2025-06-11\" rel=\"noopener noreferrer\">Wondershare PDFelement</a>.</em></p>","score":7},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"I Spent 16 Months Building My Own SaaS Solo — Why the Last 10% Takes Months","link":"https://dev.to/joshmsn/i-spent-16-months-building-my-own-saas-solo-the-last-10-took-longer-than-the-first-90-288","pubDate":"Wed, 22 Jul 2026 09:18:29 +0000","description":"<p>16 months. Several hundred hours alongside client work. One finished product.</p>\n\n<p>I built <a href=\"https://laizynote.eu\" rel=\"noopener noreferrer\">LaizyNote</a> — a business operating system for freelancers and solo entrepreneurs: notes, tasks, projects, time tracking, contacts, calendar, documents and an AI assistant in a single interface.</p>\n\n<p>I've written plenty about client projects. This is different — this one is mine. And that changes everything: the perspective, the responsibility, and the honest realization of how much a \"finished\" SaaS actually takes.</p>\n\n<p>No code in this one. Just the parts that were genuinely hard.</p>\n\n\n\n\n<h2>\n  \n  \n  Why build your own SaaS?\n</h2>\n\n<p>Out of my own need. It bugged me that there was no tool you could start with for free, that had everything I needed — and that would later also help me <em>run</em> my business. The big names can do a lot, but they're bloated: you spend weeks setting up before you jot down your first thought.</p>\n\n<p>I wanted the opposite: start instantly, yet have everything on board when you need it. While building, it became clear this isn't a niche — it's a gap. Freelancers juggle five tools (notes here, time tracking there, invoices somewhere else) and none of them shows how their business is actually doing.</p>\n\n<p>That last part — <strong>Business Insights</strong> — is the one feature I'd single out. Because LaizyNote already knows your time, projects, clients and revenue, it can derive the numbers you'd otherwise keep in a spreadsheet: your <em>real</em> hourly rate (not the one you bill), the trend over years, client health, whether your portfolio leans too heavily on one client. Other tools show you what you did. This shows you what it earned you.</p>\n\n<h2>\n  \n  \n  Hard part #1: keeping an overview at scale\n</h2>\n\n<p>The biggest technical challenge wasn't a single difficult feature. It was keeping an overview: where which data lives, where it flows, who is allowed to see what — and making sure it stays that way while ten other things change.</p>\n\n<p>LaizyNote runs on Firebase (Firestore + server-side Cloud Functions, hosted in the EU). Every user has a personal space but can also share team workspaces with roles and permissions. That's where it gets tricky: every single query has to know whether it concerns personal or shared team data, and whether the user may access it at all.</p>\n\n<p>With other people's data you can't afford a single mistake. So the access rules live server-side — the server decides what someone may see, never the browser. Everything follows the same patterns: consistent data paths, workspace-specific caches, centrally defined rules instead of special cases per feature. It sounds bureaucratic. It's the only way a new feature doesn't quietly break three old ones.</p>\n\n<h2>\n  \n  \n  Hard part #2: why the last 10% takes months\n</h2>\n\n<p>Prototyping is incredibly fast today. A new module stands in days. What eats time is the polish — the last 10% that separates a hobby project from a product people trust with their work.</p>\n\n<p>That 10% is made of things nobody notices as long as they work, and that stand out immediately when they don't:</p>\n\n<ul>\n<li>What happens with empty data, on the first login, with 10,000 entries?</li>\n<li>A link that should open the right plan straight after sign-up — and quietly fails if a single parameter is missing.</li>\n<li>A bonus offer that accidentally locks out everyone who ever deleted an account.</li>\n<li>A CSS rule that silently shifts the entire app by 16 pixels.</li>\n<li>Six languages that have to be maintained with every new line of text.</li>\n</ul>\n\n<p>None of these is big on its own. But there are hundreds of them, and each one wants to be found, understood and cleanly solved.</p>\n\n<p>The lesson that stuck: <strong>passing tests don't prove it works in the real app.</strong> Some bugs only show up when you operate it yourself in the browser. So a lot gets tested by hand — especially anything involving real money or other people's data.</p>\n\n<h2>\n  \n  \n  Hard part #3: building AI into a product\n</h2>\n\n<p>Daisy is the AI assistant in LaizyNote. You write to her in plain language and she can act: create tasks, structure projects, link contacts, summarize time and numbers. \"Create a project <em>Website</em> with three tasks for Emma\" becomes exactly those entries.</p>\n\n<p>The crucial part: <strong>she asks before every write action.</strong> She shows a preview — \"5 tasks will be created\" — and only a confirm click executes it. AI that writes into your data on its own, without you watching, would be a breach of trust.</p>\n\n<p>Building AI into a real product brings a challenge classic development doesn't have: <strong>it's not predictable.</strong> The same question can be answered slightly differently twice. The model doesn't always stick to the required format. Answers sometimes run too long mid-sentence. None of this is checkable with \"is the code correct\" — it has to be tried by hand, over and over. Two constraints mattered most: privacy (Daisy runs through a European AI provider, EU-hosted — fitting for a <code>.eu</code> product) and cost control (usage quotas and a fixed monthly cap per user, so neither the user nor I get a nasty surprise).</p>\n\n<h2>\n  \n  \n  The part I underestimated\n</h2>\n\n<p>The biggest surprise wasn't the technology. It was how much is needed <em>around</em> the product before you can bill a single customer.</p>\n\n<p>A SaaS isn't just an app. It's a brand with a name, logo and voice. Legal texts, terms, a privacy policy, a service description that holds up contractually. Payment processing with subscriptions, cancellations, yearly and monthly prices, vouchers and invoices. Automated emails, backups, and processes for when someone deletes their account. Users never see most of it — but without that foundation there's no product you can trust.</p>\n\n<p>I thought building the product was the work. In truth the product is one half; the other is brand, law, payment and operations.</p>\n\n<h2>\n  \n  \n  Would I do it again?\n</h2>\n\n<p>Yes — much of it exactly the same. Firebase was the right call for a one-person project with a large feature set: it takes backup, scaling and auth off your plate so you can focus on the product. What I'd change is the planning — I'd cut some data structures differently from the start (things you only understand once you've built them wrong), and I'd budget for all the surrounding work earlier instead of treating it as \"I'll do that at the end\".</p>\n\n<p>The result is live and publicly available, with all modules in use. Anyone can start for free — which was the whole point from the beginning.</p>\n\n\n\n\n<p><strong>Takeaways if you're building your own SaaS:</strong></p>\n\n<ol>\n<li>Prototyping is fast; the polish is the real work — plan for it.</li>\n<li>With other people's data, the server decides, never the browser.</li>\n<li>AI is unpredictable — it needs manual testing and clear, honest limits.</li>\n<li>A SaaS is half product, half brand/law/operations.</li>\n<li>Green tests are no proof. When in doubt, use it yourself.</li>\n</ol>\n\n<p><em>Full write-up (and the product) on <a href=\"https://hafenpixel.de/en/behind-the-scenes/building-my-own-saas-laizynote\" rel=\"noopener noreferrer\">hafenpixel.de</a>. You can <a href=\"https://laizynote.eu\" rel=\"noopener noreferrer\">try LaizyNote for free</a> — no credit card. Happy to answer questions in the comments.</em></p>","score":3},{"source":"https://dev.to/feed/tag/nuxt","sourceHost":"dev.to","title":"Nuxt vs SvelteKit: What works better?","link":"https://dev.to/erikch/nuxt-vs-sveltekit-what-works-better-132h","pubDate":"Wed, 22 Jul 2026 01:13:24 +0000","description":"<p>Nuxt vs SvelteKit. Which one is better?</p>\n\n<p>That is is what I've been testing out this week. I built the same app twice. Once in Nuxt with the version 5 compatibility preview turned on, and once in SvelteKit using its experimental remote functions.</p>\n\n<p>I created a basic task app. Where you can add or remove tasks. I also logged the network requests and timing. The biggest thing I noticed was that the SvelteKit app would make one request, when creating a new task, while the Nuxt app needed two.</p>\n\n<p>That one difference turned out to be the most interesting part of the whole comparison, so let's jump in.</p>\n\n<h2>\n  \n  \n  The setup\n</h2>\n\n<p>Both apps are a simple task list backed by an in browser memory store. Both are production builds running locally, and both render the initial task list on the server.</p>\n\n<p>Quick caveat, Nuxt 5 is not released yet. My Nuxt app is stable Nuxt 4.5 with the compatibility flag set:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// nuxt.config.ts</span>\n<span class=\"k\">export</span> <span class=\"k\">default</span> <span class=\"nf\">defineNuxtConfig</span><span class=\"p\">({</span>\n  <span class=\"na\">compatibilityDate</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">2026-07-01</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">future</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n    <span class=\"na\">compatibilityVersion</span><span class=\"p\">:</span> <span class=\"mi\">5</span><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>And SvelteKit's remote functions are still marked experimental in the docs. So this is a comparison of directions, not finished products.</p>\n\n<h2>\n  \n  \n  The numbers\n</h2>\n\n<p>When I add a task in the Nuxt app, I get a POST to <code>/api/tasks</code> (about 690 ms) followed by a GET to <code>/api/tasks</code> (about 450 ms) to refresh the list. A little over 1,100 ms total, and the timeline panel reports two browser requests.</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%2Fbv3bxswcuz5iy702h5ns.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%2Fbv3bxswcuz5iy702h5ns.png\" alt=\"Nuxt request\" width=\"800\" height=\"402\"></a></p>\n\n<p>When I add a task in the SvelteKit app, I get one request. About 1,100 ms. The server still does both operations, the mutation and the read, but they come back in a single response.</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%2Fur0jeepdlow1acyb8w7d.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%2Fur0jeepdlow1acyb8w7d.png\" alt=\"Svelte Request\" width=\"800\" height=\"385\"></a></p>\n\n<p>Plain refreshes were nearly identical. 447 ms in Nuxt, 448 ms in SvelteKit. I ran this quite a few times, and if I had to pick, SvelteKit felt slightly faster overall. But the totals were close enough that I wouldn't choose a framework based on them.</p>\n\n<p>Let's talk about how requests work in each. </p>\n\n<h2>\n  \n  \n  The Nuxt version: explicit API routes\n</h2>\n\n<p>If you've used Nuxt before, this will feel familiar. I have two handlers in <code>server/api</code>:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// server/api/tasks.get.ts</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">defineEventHandler</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">h3</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">readTasks</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">../utils/task-store</span><span class=\"dl\">'</span>\n\n<span class=\"k\">export</span> <span class=\"k\">default</span> <span class=\"nf\">defineEventHandler</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nf\">readTasks</span><span class=\"p\">())</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The POST handler validates the title and saves the task. These are normal HTTP endpoints. Anything that speaks HTTP can call them.</p>\n\n<p>On the page, <code>useFetch</code> loads the initial data during SSR, so hydration doesn't fetch it again. When I add a task, I post with <code>$fetch</code> and then call <code>refresh()</code>:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">const</span> <span class=\"p\">{</span> <span class=\"na\">data</span><span class=\"p\">:</span> <span class=\"nx\">snapshot</span><span class=\"p\">,</span> <span class=\"nx\">refresh</span> <span class=\"p\">}</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">useFetch</span><span class=\"o\">&lt;</span><span class=\"nx\">TaskSnapshot</span><span class=\"o\">&gt;</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/api/tasks</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n  <span class=\"na\">key</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">task-dashboard</span><span class=\"dl\">'</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\">submitTask</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"k\">await</span> <span class=\"nf\">$fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/api/tasks</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n    <span class=\"na\">method</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">POST</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n    <span class=\"na\">body</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">requestId</span><span class=\"p\">:</span> <span class=\"nx\">crypto</span><span class=\"p\">.</span><span class=\"nf\">randomUUID</span><span class=\"p\">(),</span> <span class=\"nx\">title</span> <span class=\"p\">},</span>\n  <span class=\"p\">})</span>\n  <span class=\"k\">await</span> <span class=\"nf\">refresh</span><span class=\"p\">()</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The code is explicit, and the network tab matches the code exactly. POST first, GET second.</p>\n\n<p>Could I avoid the second request? Sure. The POST could return the updated list and I could patch local state myself. I wrote it this way because invalidate-and-refetch is the workflow most of us reach for, and it's exactly the pattern SvelteKit's remote functions are designed to improve. I also find by adding a GET request we are verifying the exact output after the POST.</p>\n\n<h2>\n  \n  \n  The SvelteKit version: remote functions\n</h2>\n\n<p>This is the experimental feature. You turn it on in <code>svelte.config.js</code>:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const</span> <span class=\"nx\">config</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"na\">kit</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n    <span class=\"na\">adapter</span><span class=\"p\">:</span> <span class=\"nf\">adapter</span><span class=\"p\">(),</span>\n    <span class=\"na\">experimental</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n      <span class=\"na\">remoteFunctions</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n    <span class=\"p\">},</span>\n  <span class=\"p\">},</span>\n  <span class=\"na\">compilerOptions</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n    <span class=\"na\">experimental</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n      <span class=\"na\">async</span><span class=\"p\">:</span> <span class=\"kc\">true</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>Then you create a file ending in <code>.remote.ts</code> and export your server functions:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// tasks.remote.ts</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">command</span><span class=\"p\">,</span> <span class=\"nx\">query</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">$app/server</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">addTask</span> <span class=\"k\">as</span> <span class=\"nx\">addTaskToStore</span><span class=\"p\">,</span> <span class=\"nx\">readTasks</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">$lib/server/task-store</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"o\">*</span> <span class=\"k\">as</span> <span class=\"nx\">v</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">valibot</span><span class=\"dl\">'</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">taskInput</span> <span class=\"o\">=</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">object</span><span class=\"p\">({</span>\n  <span class=\"na\">requestId</span><span class=\"p\">:</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span><span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">string</span><span class=\"p\">(),</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">trim</span><span class=\"p\">(),</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">minLength</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">maxLength</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">)),</span>\n  <span class=\"na\">title</span><span class=\"p\">:</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span><span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">string</span><span class=\"p\">(),</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">trim</span><span class=\"p\">(),</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">minLength</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"nx\">v</span><span class=\"p\">.</span><span class=\"nf\">maxLength</span><span class=\"p\">(</span><span class=\"mi\">80</span><span class=\"p\">)),</span>\n<span class=\"p\">})</span>\n\n<span class=\"k\">export</span> <span class=\"kd\">const</span> <span class=\"nx\">getTasks</span> <span class=\"o\">=</span> <span class=\"nf\">query</span><span class=\"p\">(</span><span class=\"k\">async </span><span class=\"p\">()</span> <span class=\"o\">=&gt;</span> <span class=\"nf\">readTasks</span><span class=\"p\">())</span>\n\n<span class=\"k\">export</span> <span class=\"kd\">const</span> <span class=\"nx\">addTask</span> <span class=\"o\">=</span> <span class=\"nf\">command</span><span class=\"p\">(</span><span class=\"nx\">taskInput</span><span class=\"p\">,</span> <span class=\"k\">async </span><span class=\"p\">({</span> <span class=\"nx\">requestId</span><span class=\"p\">,</span> <span class=\"nx\">title</span> <span class=\"p\">})</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">result</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">addTaskToStore</span><span class=\"p\">(</span><span class=\"nx\">title</span><span class=\"p\">,</span> <span class=\"nx\">requestId</span><span class=\"p\">)</span>\n\n  <span class=\"c1\">// This runs on the server, and the refreshed query value</span>\n  <span class=\"c1\">// comes back in the same command response.</span>\n  <span class=\"k\">void</span> <span class=\"nf\">getTasks</span><span class=\"p\">().</span><span class=\"nf\">refresh</span><span class=\"p\">()</span>\n\n  <span class=\"k\">return</span> <span class=\"nx\">result</span>\n<span class=\"p\">})</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The function bodies always run on the server. In the browser, they become typed wrappers around endpoints SvelteKit generates for you. I really enjoy how there is no public endpoint to hit, it's isolated. It's created for the call, which means I can use environment variables and secrets in there without thinking about it.</p>\n\n<p>The <code>void getTasks().refresh()</code> line is the API on the server side to trigger the refresh. After the mutation, SvelteKit refreshes the query on the server and packages the new value into the command's response. That's the single-flight mutation, and it's why the network tab shows one request instead of two.</p>\n\n<p>On the page, I just import and call the functions:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight svelte\"><code><span class=\"nt\">&lt;script </span><span class=\"na\">lang=</span><span class=\"s\">\"ts\"</span><span class=\"nt\">&gt;</span>\n  <span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">addTask</span><span class=\"p\">,</span> <span class=\"nx\">getTasks</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">./tasks.remote</span><span class=\"dl\">'</span>\n\n  <span class=\"kd\">const</span> <span class=\"nx\">tasks</span> <span class=\"o\">=</span> <span class=\"nf\">getTasks</span><span class=\"p\">()</span>\n\n  <span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">submitTask</span><span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">:</span> <span class=\"nx\">SubmitEvent</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nf\">preventDefault</span><span class=\"p\">()</span>\n    <span class=\"k\">await</span> <span class=\"nf\">addTask</span><span class=\"p\">({</span> <span class=\"na\">requestId</span><span class=\"p\">:</span> <span class=\"nx\">crypto</span><span class=\"p\">.</span><span class=\"nf\">randomUUID</span><span class=\"p\">(),</span> <span class=\"nx\">title</span> <span class=\"p\">})</span>\n  <span class=\"p\">}</span>\n<span class=\"nt\">&lt;/script&gt;</span>\n\n<span class=\"si\">{</span><span class=\"p\">@</span><span class=\"nd\">render</span> <span class=\"nf\">dashboard</span><span class=\"p\">(</span><span class=\"k\">await</span> <span class=\"nx\">tasks</span><span class=\"p\">)</span><span class=\"si\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>That <code>await tasks</code> at the bottom works almost like a subscription. When the server-side refresh happens, the task list updates automatically. No server routes to worry 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%2F24q7zjeyi2ru8skt8l6t.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%2F24q7zjeyi2ru8skt8l6t.png\" alt=\"remotefunctions\" width=\"799\" height=\"417\"></a></p>\n\n<p>My first look at this pattern, I thought it was a little complicated. But it clicked pretty fast, and the query-refreshes-inside-the-command idea makes sense once you see it in the network tab. If you've used server actions in Next or TanStack Start, this will feel like family. I'd say I still like TanStack Start's server actions a bit better, but this is close.</p>\n\n<blockquote>\n<p><strong>Heads up</strong>: remote functions have been available since SvelteKit 2.27 and they are still experimental. The API has changed several times over the past few months. If you adopt them early, pin your versions and budget time for migrations.</p>\n</blockquote>\n\n<h2>\n  \n  \n  What does Nuxt 5 offer?\n</h2>\n\n<p>Nuxt 5 doesn't have an answer to remote functions. Most of the confirmed work is in the underlying structure. A new version of Nitro, a new Vite integration, and framework internals. The <a href=\"https://nuxt.com/docs/getting-started/upgrade\" rel=\"noopener noreferrer\">upgrade guide</a> walks through what to expect, and it's mostly foundation work rather than new application-level APIs.</p>\n\n<h2>\n  \n  \n  My verdict\n</h2>\n\n<p>I'm sticking with Nuxt. I love the API routes pattern, I love Vue, and nothing in this demo is a reason to rewrite an existing app. You can return updated data from a mutation today and skip the second request yourself if it matters.</p>\n\n<p>But I do miss server actions. Frameworks like SvelteKit, Next, and TanStack Start all have some version of a typed, non-public server function you can call from a component. I hope Nuxt adds something like it in a future update, beyond the server components they have now.</p>\n\n<p>If you're starting a SvelteKit project and can live with an experimental API, try remote functions first. The single-flight mutation is awesome, and the types crossing the boundary for free is a great developer experience.</p>\n\n<p>Which side are you on: explicit API routes, or remote functions that generate the transport for you? Let me know in the comments if you agree or disagree. </p>\n\n<p>BTW, I used <a href=\"https://kiro.dev\" rel=\"noopener noreferrer\">Kiro</a> for all my research for this post and video! Check it out , it's an amazing harness!</p>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"How to build interactive experiences with canvases","link":"https://github.blog/ai-and-ml/github-copilot/how-to-build-interactive-experiences-with-canvases/","pubDate":"Tue, 21 Jul 2026 16:00:00 +0000","description":"<p>Canvases turn AI into interactive workspaces where you can visualize information, explore workflows, and take action across complex tasks.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/github-copilot/how-to-build-interactive-experiences-with-canvases/\">How to build interactive experiences with canvases</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"How to Crop PDF Pages in the Browser with Vue 3 and pdf-lib","link":"https://dev.to/sunshey/how-to-crop-pdf-pages-in-the-browser-with-vue-3-and-pdf-lib-5d75","pubDate":"Tue, 21 Jul 2026 13:15:53 +0000","description":"<p>Cropping PDF pages sounds like a server-side operation, but with <code>pdf-lib</code> and a <code>&lt;canvas&gt;</code> preview, you can do it entirely in the browser. No upload, no backend, and full control over margins.</p>\n\n<p>This post walks through a minimal but production-ready implementation you can drop into a Vue 3 project.</p>\n\n<h2>\n  \n  \n  Why client-side?\n</h2>\n\n<p>Traditional PDF croppers upload your file, crop it on a server, and send it back. That works, but it introduces latency, bandwidth costs, and privacy risk. A browser-based cropper:</p>\n\n<ul>\n<li>Keeps files on the user's device</li>\n<li>Works offline after the app loads</li>\n<li>Avoids server-side processing entirely</li>\n</ul>\n\n<h2>\n  \n  \n  The stack\n</h2>\n\n<ul>\n<li>\n<strong>Vue 3</strong> with Composition API</li>\n<li>\n<strong>pdf-lib</strong> for PDF manipulation</li>\n<li>\n<strong>PDF.js</strong> for page preview rendering</li>\n<li>\n<strong>Canvas</strong> for interactive crop region selection</li>\n<li>\n<strong>Vite</strong> for bundling</li>\n</ul>\n\n<h2>\n  \n  \n  Minimal implementation\n</h2>\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=\"na\">lang=</span><span class=\"s\">\"ts\"</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=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">vue</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">PDFDocument</span><span class=\"p\">,</span> <span class=\"nx\">rgb</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">pdf-lib</span><span class=\"dl\">'</span>\n<span class=\"k\">import</span> <span class=\"o\">*</span> <span class=\"k\">as</span> <span class=\"nx\">pdfjs</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">pdfjs-dist</span><span class=\"dl\">'</span>\n\n<span class=\"nx\">pdfjs</span><span class=\"p\">.</span><span class=\"nx\">GlobalWorkerOptions</span><span class=\"p\">.</span><span class=\"nx\">workerSrc</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">/pdf.worker.min.js</span><span class=\"dl\">'</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">file</span> <span class=\"o\">=</span> <span class=\"nx\">ref</span><span class=\"o\">&lt;</span><span class=\"nx\">File</span> <span class=\"o\">|</span> <span class=\"kc\">null</span><span class=\"o\">&gt;</span><span class=\"p\">(</span><span class=\"kc\">null</span><span class=\"p\">)</span>\n<span class=\"kd\">const</span> <span class=\"nx\">cropping</span> <span class=\"o\">=</span> <span class=\"nf\">ref</span><span class=\"p\">(</span><span class=\"kc\">false</span><span class=\"p\">)</span>\n<span class=\"kd\">const</span> <span class=\"nx\">margins</span> <span class=\"o\">=</span> <span class=\"nf\">ref</span><span class=\"p\">({</span> <span class=\"na\">top</span><span class=\"p\">:</span> <span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"na\">bottom</span><span class=\"p\">:</span> <span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"na\">left</span><span class=\"p\">:</span> <span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"na\">right</span><span class=\"p\">:</span> <span class=\"mi\">50</span> <span class=\"p\">})</span>\n\n<span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">handleFileUpload</span><span class=\"p\">(</span><span class=\"nx\">selected</span><span class=\"p\">:</span> <span class=\"nx\">File</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n  <span class=\"nx\">file</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"o\">=</span> <span class=\"nx\">selected</span>\n<span class=\"p\">}</span>\n\n<span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">cropPdf</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">file</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">)</span> <span class=\"k\">return</span>\n  <span class=\"nx\">cropping</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"o\">=</span> <span class=\"kc\">true</span>\n\n  <span class=\"k\">try</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">arrayBuffer</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">file</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nf\">arrayBuffer</span><span class=\"p\">()</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">pdf</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">PDFDocument</span><span class=\"p\">.</span><span class=\"nf\">load</span><span class=\"p\">(</span><span class=\"nx\">arrayBuffer</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">pages</span> <span class=\"o\">=</span> <span class=\"nx\">pdf</span><span class=\"p\">.</span><span class=\"nf\">getPages</span><span class=\"p\">()</span>\n\n    <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">page</span> <span class=\"k\">of</span> <span class=\"nx\">pages</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"kd\">const</span> <span class=\"p\">{</span> <span class=\"nx\">width</span><span class=\"p\">,</span> <span class=\"nx\">height</span> <span class=\"p\">}</span> <span class=\"o\">=</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">getSize</span><span class=\"p\">()</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">m</span> <span class=\"o\">=</span> <span class=\"nx\">margins</span><span class=\"p\">.</span><span class=\"nx\">value</span>\n\n      <span class=\"c1\">// Clamp margins to page dimensions</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">cropLeft</span> <span class=\"o\">=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">left</span><span class=\"p\">,</span> <span class=\"nx\">width</span> <span class=\"o\">/</span> <span class=\"mi\">2</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">)</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">cropRight</span> <span class=\"o\">=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">right</span><span class=\"p\">,</span> <span class=\"nx\">width</span> <span class=\"o\">/</span> <span class=\"mi\">2</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">)</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">cropTop</span> <span class=\"o\">=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">top</span><span class=\"p\">,</span> <span class=\"nx\">height</span> <span class=\"o\">/</span> <span class=\"mi\">2</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">)</span>\n      <span class=\"kd\">const</span> <span class=\"nx\">cropBottom</span> <span class=\"o\">=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">bottom</span><span class=\"p\">,</span> <span class=\"nx\">height</span> <span class=\"o\">/</span> <span class=\"mi\">2</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">)</span>\n\n      <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">setCropBox</span><span class=\"p\">(</span>\n        <span class=\"nx\">cropLeft</span><span class=\"p\">,</span>\n        <span class=\"nx\">cropBottom</span><span class=\"p\">,</span>\n        <span class=\"nx\">width</span> <span class=\"o\">-</span> <span class=\"nx\">cropLeft</span> <span class=\"o\">-</span> <span class=\"nx\">cropRight</span><span class=\"p\">,</span>\n        <span class=\"nx\">height</span> <span class=\"o\">-</span> <span class=\"nx\">cropTop</span> <span class=\"o\">-</span> <span class=\"nx\">cropBottom</span>\n      <span class=\"p\">)</span>\n    <span class=\"p\">}</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">croppedBytes</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">pdf</span><span class=\"p\">.</span><span class=\"nf\">save</span><span class=\"p\">()</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">blob</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">Blob</span><span class=\"p\">([</span><span class=\"nx\">croppedBytes</span><span class=\"p\">],</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">application/pdf</span><span class=\"dl\">'</span> <span class=\"p\">})</span>\n    <span class=\"nf\">downloadBlob</span><span class=\"p\">(</span><span class=\"nx\">blob</span><span class=\"p\">,</span> <span class=\"dl\">'</span><span class=\"s1\">cropped.pdf</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n  <span class=\"p\">}</span> <span class=\"k\">finally</span> <span class=\"p\">{</span>\n    <span class=\"nx\">cropping</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"o\">=</span> <span class=\"kc\">false</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n\n<span class=\"kd\">function</span> <span class=\"nf\">downloadBlob</span><span class=\"p\">(</span><span class=\"nx\">blob</span><span class=\"p\">:</span> <span class=\"nx\">Blob</span><span class=\"p\">,</span> <span class=\"nx\">name</span><span class=\"p\">:</span> <span class=\"nx\">string</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">url</span> <span class=\"o\">=</span> <span class=\"nx\">URL</span><span class=\"p\">.</span><span class=\"nf\">createObjectURL</span><span class=\"p\">(</span><span class=\"nx\">blob</span><span class=\"p\">)</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">a</span> <span class=\"o\">=</span> <span class=\"nb\">document</span><span class=\"p\">.</span><span class=\"nf\">createElement</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">a</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n  <span class=\"nx\">a</span><span class=\"p\">.</span><span class=\"nx\">href</span> <span class=\"o\">=</span> <span class=\"nx\">url</span>\n  <span class=\"nx\">a</span><span class=\"p\">.</span><span class=\"nx\">download</span> <span class=\"o\">=</span> <span class=\"nx\">name</span>\n  <span class=\"nx\">a</span><span class=\"p\">.</span><span class=\"nf\">click</span><span class=\"p\">()</span>\n  <span class=\"nx\">URL</span><span class=\"p\">.</span><span class=\"nf\">revokeObjectURL</span><span class=\"p\">(</span><span class=\"nx\">url</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</code></pre>\n\n</div>\n\n\n\n<p>The key method is <code>page.setCropBox(left, bottom, width, height)</code>. It modifies the crop box — the visible area when the page is displayed. Unlike the media box (physical page size), the crop box doesn't change the underlying content; it just hides what's outside the visible region.</p>\n\n<h2>\n  \n  \n  Interactive crop preview\n</h2>\n\n<p>For a better UX, render the page with PDF.js on a canvas and draw an overlay rectangle showing the crop region. Users can drag the edges of the rectangle to adjust margins visually:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">function</span> <span class=\"nf\">drawCropOverlay</span><span class=\"p\">(</span><span class=\"nx\">canvas</span><span class=\"p\">:</span> <span class=\"nx\">HTMLCanvasElement</span><span class=\"p\">,</span> <span class=\"nx\">page</span><span class=\"p\">:</span> <span class=\"nx\">pdfjs</span><span class=\"p\">.</span><span class=\"nx\">PageViewport</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">ctx</span> <span class=\"o\">=</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nf\">getContext</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2d</span><span class=\"dl\">'</span><span class=\"p\">)</span><span class=\"o\">!</span>\n  <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">clearRect</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">)</span>\n\n  <span class=\"c1\">// Draw the page</span>\n  <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nf\">draw</span><span class=\"p\">(</span><span class=\"nx\">ctx</span><span class=\"p\">).</span><span class=\"nx\">promise</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=\"c1\">// Draw semi-transparent overlay outside crop region</span>\n    <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">fillStyle</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">rgba(0, 0, 0, 0.5)</span><span class=\"dl\">'</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">m</span> <span class=\"o\">=</span> <span class=\"nx\">margins</span><span class=\"p\">.</span><span class=\"nx\">value</span>\n    <span class=\"c1\">// Top strip</span>\n    <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">fillRect</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">top</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">))</span>\n    <span class=\"c1\">// Bottom strip</span>\n    <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">fillRect</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">*</span> <span class=\"p\">((</span><span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">-</span> <span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">bottom</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">),</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">bottom</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">))</span>\n    <span class=\"c1\">// Left strip</span>\n    <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">fillRect</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">top</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">),</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">left</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">width</span><span class=\"p\">),</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"mi\">1</span> <span class=\"o\">-</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">top</span> <span class=\"o\">+</span> <span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">bottom</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">))</span>\n    <span class=\"c1\">// Right strip</span>\n    <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nf\">fillRect</span><span class=\"p\">(</span><span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span> <span class=\"o\">*</span> <span class=\"p\">((</span><span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">width</span> <span class=\"o\">-</span> <span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">right</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">width</span><span class=\"p\">),</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">top</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">),</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">width</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">right</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">width</span><span class=\"p\">),</span> <span class=\"nx\">canvas</span><span class=\"p\">.</span><span class=\"nx\">height</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"mi\">1</span> <span class=\"o\">-</span> <span class=\"p\">(</span><span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">top</span> <span class=\"o\">+</span> <span class=\"nx\">m</span><span class=\"p\">.</span><span class=\"nx\">bottom</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"nx\">page</span><span class=\"p\">.</span><span class=\"nx\">height</span><span class=\"p\">))</span>\n  <span class=\"p\">})</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h2>\n  \n  \n  UX tips from a live tool\n</h2>\n\n<p>At <a href=\"https://en.sotool.top/crop-pdf\" rel=\"noopener noreferrer\">en.sotool.top/crop-pdf</a>, we learned a few things from real users:</p>\n\n<ol>\n<li>\n<strong>Provide presets.</strong> Most users just want to remove scan borders. Give them a one-click solution.</li>\n<li>\n<strong>Allow page-specific settings.</strong> Not every page in a document has the same margins.</li>\n<li>\n<strong>Show a before/after preview.</strong> Users need to see the result before downloading.</li>\n<li>\n<strong>Explain the difference between crop and trim.</strong> Crop hides content; trim destroys it.</li>\n</ol>\n\n<h2>\n  \n  \n  Going further\n</h2>\n\n<p>For simple PDF cropping, <code>pdf-lib</code> plus a canvas preview is enough. If you need batch processing, OCR, or conversion to editable formats, you'll want a desktop tool.</p>\n\n<p>Want to see the full source? The site is built in public at <a href=\"https://github.com/sunshey/pdf-tool\" rel=\"noopener noreferrer\">github.com/sunshey/pdf-tool</a>.</p>\n\n\n\n\n<p><em>If you need desktop-grade PDF editing — OCR, batch cropping, or advanced export formats — check out <a href=\"https://www.anrdoezrs.net/click-101775418-14071692?url=https%3A%2F%2Fpdf.wondershare.com%2F%3Futm_source%3Dcommission-junction%26utm_medium%3Daffiliate%26utm_campaign%3D3months_epc_high_low%26utm_content%3Dlink_25136016_2025-06-11\" rel=\"noopener noreferrer\">Wondershare PDFelement</a>.</em></p>","score":3},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"Shipping a coffee storefront before the data model was ready","link":"https://dev.to/oleksandr_devops/shipping-a-coffee-storefront-before-the-data-model-was-ready-7a7","pubDate":"Tue, 21 Jul 2026 12:21:12 +0000","description":"<p>We just shipped two big surfaces for <a href=\"https://brewly.online\" rel=\"noopener noreferrer\"><strong>Brewly Store</strong></a>: a <strong>Catalog / Shop</strong> page and a full <strong>product page (PDP)</strong>. On the surface it's an ordinary e-commerce release — a filterable grid, a product detail view with size and grind pickers, a taste profile. The interesting part isn't the features. It's that we shipped all of it <em>before</em> the backend <code>GET /products</code> endpoint existed, and the whole release hinged on one discipline: <strong>never let a placeholder pretend to be a working feature.</strong></p>\n\n<p>Here's what that looks like in the actual code.</p>\n\n<h2>\n  \n  \n  The project\n</h2>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D1nNHKlPBhSCiUcpfC1s90DIkYRh7Eop_A%26export%3Ddownload\" 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%2Fdrive.google.com%2Fuc%3Fid%3D1nNHKlPBhSCiUcpfC1s90DIkYRh7Eop_A%26export%3Ddownload\" alt=\"Brewly Store homepage — \" width=\"1341\" height=\"605\"></a></p>\n\n<p><a href=\"https://brewly.online\" rel=\"noopener noreferrer\"><strong>Brewly Store</strong></a> is a coffee e-commerce platform running on Cloudflare's edge:</p>\n\n<ul>\n<li>\n<strong>Frontend</strong> — a Nuxt 4 storefront (Vue 3, Pinia, TailwindCSS) on Cloudflare Pages.</li>\n<li>\n<strong>Backend</strong> — a <a href=\"https://developers.cloudflare.com/workers/\" rel=\"noopener noreferrer\">Cloudflare Workers</a> API in <a href=\"https://hono.dev/\" rel=\"noopener noreferrer\">Hono</a> with <code>@hono/zod-openapi</code>, backed by Cloudflare <strong>D1</strong> (SQLite at the edge).</li>\n<li>\n<strong>i18n</strong> — every surface is bilingual (EN / UA); the Ukrainian route is just <code>/uk</code> in front of the path, e.g. <code>brewly.online/uk/catalog/coffee</code>.</li>\n</ul>\n\n<p>At release time the products themselves still live in a static frontend module (<code>app/data/products.ts</code>) — the <code>GET /products</code> API isn't there yet. That single fact shaped every decision below.</p>\n\n<h2>\n  \n  \n  What shipped\n</h2>\n\n<p><strong>Catalog / Shop</strong> — <a href=\"https://brewly.online/catalog/coffee\" rel=\"noopener noreferrer\"><code>/catalog/coffee</code></a>: a \"Brewly Shop\" header with a live product count, sorting (Featured / Price ↑ / Price ↓), a filter sidebar (Roast level, Type, Grind size, Taste notes, Price), a 3-column grid with black \"Add to Cart\" buttons, and \"Load more\" pagination with numbered pages.</p>\n\n<p><strong>Product page (PDP)</strong> — e.g. <a href=\"https://brewly.online/product/ethiopia-yirgacheffe\" rel=\"noopener noreferrer\"><code>/product/ethiopia-yirgacheffe</code></a>: breadcrumbs, a taste rating drawn as coffee beans (Bitterness / Sweetness / Acidity), size (250 g / 1000 g) and grind selection, a quantity stepper, a reactive price, accordions (Shipping / How to brew / Origin details), a Taste Profile block, and a \"You may like\" section.</p>\n\n<p>All of it renders, responds, and looks finished. Behind it, the data was uneven — and that's where the real work was.</p>\n\n<h2>\n  \n  \n  The real story: shipping ahead of the data\n</h2>\n\n<p>We had <strong>four real products</strong> with confirmed data and dedicated pages — <code>brazil-santos</code>, <code>colombia-supremo</code>, <code>ethiopia-yirgacheffe</code>, <code>kenya-nyeri</code> — and a design that called for attributes and inventory the backend didn't carry yet. The temptation there is to fake it: fill the grid with fake cards, wire up filters that don't filter, and hope nobody clicks. That collapses the first time a real user clicks a control that does nothing.</p>\n\n<p>We took the opposite approach — ship what's real as <em>real</em>, and mark everything else as visibly provisional. Three decisions carried the release.</p>\n\n<h3>\n  \n  \n  1. Real filters vs. presentational placeholders\n</h3>\n\n<p><a href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D1BwxEvRc0olq0Tyl_RNz8M_pgLrvQAL5R%26export%3Ddownload\" 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%2Fdrive.google.com%2Fuc%3Fid%3D1BwxEvRc0olq0Tyl_RNz8M_pgLrvQAL5R%26export%3Ddownload\" alt=\"Brewly Shop catalog — Roast level filter, 3-column grid, black Add to Cart buttons\" width=\"1341\" height=\"605\"></a></p>\n\n<p>Two filters are backed by actual product data and genuinely narrow the grid: <strong>Roast level</strong> and <strong>Price</strong>. The other three — Type, Grind size, Taste notes — are in the design, but there's no data behind them yet. So they render as UI, but the filtering logic simply doesn't consult them. That decision is one comment and one <code>return</code> in the catalog page:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">const</span> <span class=\"nx\">filteredProducts</span> <span class=\"o\">=</span> <span class=\"nf\">computed</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span>\n  <span class=\"nx\">allProducts</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nf\">filter</span><span class=\"p\">(</span><span class=\"nx\">product</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">roastMatch</span> <span class=\"o\">=</span>\n      <span class=\"nx\">filters</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nx\">roast</span><span class=\"p\">.</span><span class=\"nx\">length</span> <span class=\"o\">===</span> <span class=\"mi\">0</span> <span class=\"o\">||</span> <span class=\"nx\">filters</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nx\">roast</span><span class=\"p\">.</span><span class=\"nf\">includes</span><span class=\"p\">(</span><span class=\"nx\">product</span><span class=\"p\">.</span><span class=\"nx\">roastKey</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">priceMatch</span> <span class=\"o\">=</span>\n      <span class=\"nx\">filters</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nx\">price</span><span class=\"p\">.</span><span class=\"nx\">length</span> <span class=\"o\">===</span> <span class=\"mi\">0</span> <span class=\"o\">||</span>\n      <span class=\"nx\">filters</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nx\">price</span><span class=\"p\">.</span><span class=\"nf\">some</span><span class=\"p\">(</span><span class=\"nx\">band</span> <span class=\"o\">=&gt;</span> <span class=\"nf\">matchesPriceBand</span><span class=\"p\">(</span><span class=\"nx\">product</span><span class=\"p\">.</span><span class=\"nx\">basePrice</span><span class=\"p\">,</span> <span class=\"nx\">band</span><span class=\"p\">))</span>\n\n    <span class=\"c1\">// Type / Grind / Taste are visual-only until the backend exposes that data.</span>\n    <span class=\"k\">return</span> <span class=\"nx\">roastMatch</span> <span class=\"o\">&amp;&amp;</span> <span class=\"nx\">priceMatch</span>\n  <span class=\"p\">})</span>\n<span class=\"p\">)</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Active filters show up as removable chips with a \"Clear all\" reset — but only Roast and Price ever change the result set, so those are the only two that can actually strand you in an empty grid. When the backend grows a <code>type</code> facet, it joins the <code>return</code> line; nothing else moves.</p>\n\n<h3>\n  \n  \n  2. Demo products vs. real products\n</h3>\n\n<p>To make an early catalog feel populated without lying about inventory, the grid mixes the four real products with clearly-separated <strong>demo fillers</strong>. Their whole reason for existing is written on the type in <code>data/products.ts</code>:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"cm\">/**\n * Placeholder catalog fillers used only to populate the 3-column shop grid and\n * pagination until the backend `GET /products` endpoint exists. These reuse the\n * four real product photos and carry hard-coded (non-i18n) proper names, since\n * they are demo SKUs, not real inventory. They are excluded from PDP linking.\n */</span>\n<span class=\"k\">export</span> <span class=\"kd\">type</span> <span class=\"nx\">DemoProductEntry</span> <span class=\"o\">=</span> <span class=\"p\">{</span> <span class=\"cm\">/* ... */</span> <span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The two lists are merged in one composable, with the demo entries flagged so nothing downstream can mistake one for the other:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">export</span> <span class=\"kd\">function</span> <span class=\"nf\">useCatalogCards</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">realCards</span> <span class=\"o\">=</span> <span class=\"nf\">useProductCards</span><span class=\"p\">()</span>\n\n  <span class=\"k\">return</span> <span class=\"nx\">computed</span><span class=\"o\">&lt;</span><span class=\"nx\">ProductCardItem</span><span class=\"p\">[]</span><span class=\"o\">&gt;</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">demoCards</span> <span class=\"o\">=</span> <span class=\"nx\">demoCatalog</span><span class=\"p\">.</span><span class=\"nf\">map</span><span class=\"p\">(</span><span class=\"nx\">entry</span> <span class=\"o\">=&gt;</span> <span class=\"p\">({</span> <span class=\"cm\">/* ...entry */</span> <span class=\"na\">isDemo</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">}))</span>\n    <span class=\"k\">return</span> <span class=\"p\">[...</span><span class=\"nx\">realCards</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">,</span> <span class=\"p\">...</span><span class=\"nx\">demoCards</span><span class=\"p\">]</span>   <span class=\"c1\">// real first, fillers after</span>\n  <span class=\"p\">})</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>And the flag is load-bearing in exactly one place that matters — the card's link. A demo card renders identically but has <strong>no</strong> navigation overlay, so it can never route to a product page that doesn't exist:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight vue\"><code><span class=\"nt\">&lt;NuxtLink</span>\n  <span class=\"na\">v-if=</span><span class=\"s\">\"!product.soldOut &amp;&amp; !product.isDemo\"</span>\n  <span class=\"na\">:to=</span><span class=\"s\">\"productTo\"</span>\n  <span class=\"na\">class=</span><span class=\"s\">\"absolute inset-0 z-[1]\"</span>\n<span class=\"nt\">/&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<p>This keeps the <em>mechanics</em> — sorting, pagination, layout — testable end-to-end today, while the real catalog fills in behind them. (The same guard keeps the prerender crawler from ever hitting a non-existent PDP.)</p>\n\n<h3>\n  \n  \n  3. Optimistic \"Add to Cart\" before there's a cart\n</h3>\n\n<p>There's no cart or checkout backend yet — our Pinia app store literally has no cart state in it. Rather than hide the primary call-to-action until it's fully wired, \"Add to Cart\" ships as an <strong>optimistic, visual-only</strong> interaction: it flips a label to \"Added to cart\" for 1.6 seconds and does nothing else.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">function</span> <span class=\"nf\">handleAddToCart</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">p</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nx\">soldOut</span><span class=\"p\">)</span> <span class=\"k\">return</span>\n\n  <span class=\"nx\">addedFlash</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"o\">=</span> <span class=\"kc\">true</span>\n  <span class=\"nf\">clearTimeout</span><span class=\"p\">(</span><span class=\"nx\">addedTimeout</span><span class=\"p\">)</span>\n  <span class=\"nx\">addedTimeout</span> <span class=\"o\">=</span> <span class=\"nf\">setTimeout</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"nx\">addedFlash</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"o\">=</span> <span class=\"kc\">false</span>\n  <span class=\"p\">},</span> <span class=\"mi\">1600</span><span class=\"p\">)</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>It's honest — it does exactly what it appears to do, no more — and it lets us validate the button's placement, states, and copy now. Swapping this stub for a real cart store action later touches one function, not the whole PDP.</p>\n\n<p>Meanwhile the parts that <em>are</em> wired are fully real. The price genuinely reacts to the size selection, because the weight options carry a multiplier:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">export</span> <span class=\"kd\">const</span> <span class=\"nx\">productWeightOptions</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n  <span class=\"p\">{</span> <span class=\"na\">label</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">250g</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"na\">multiplier</span><span class=\"p\">:</span> <span class=\"mi\">1</span> <span class=\"p\">},</span>\n  <span class=\"p\">{</span> <span class=\"na\">label</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">1000g</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"na\">multiplier</span><span class=\"p\">:</span> <span class=\"mf\">3.4</span> <span class=\"p\">}</span>\n<span class=\"p\">]</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">unitPrice</span> <span class=\"o\">=</span> <span class=\"nf\">computed</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">option</span> <span class=\"o\">=</span>\n    <span class=\"nx\">productWeightOptions</span><span class=\"p\">.</span><span class=\"nf\">find</span><span class=\"p\">(</span><span class=\"nx\">o</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">o</span><span class=\"p\">.</span><span class=\"nx\">label</span> <span class=\"o\">===</span> <span class=\"nx\">selectedWeight</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">)</span> <span class=\"o\">??</span> <span class=\"nx\">productWeightOptions</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">!</span>\n  <span class=\"k\">return</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">round</span><span class=\"p\">(</span><span class=\"nx\">p</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nx\">basePrice</span> <span class=\"o\">*</span> <span class=\"nx\">option</span><span class=\"p\">.</span><span class=\"nx\">multiplier</span><span class=\"p\">)</span>\n<span class=\"p\">})</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Same for the quantity stepper and the total. If a control moves a number, that number is real; if a control is decorative, it's decorative all the way down. No half-wired middle ground.</p>\n\n<h2>\n  \n  \n  A small thing I'm happy with: the bean rating\n</h2>\n\n<p>The taste scores (Bitterness / Sweetness / Acidity) aren't stars — they're little coffee beans, drawn as inline SVG with a gradient fill so they read as <em>beans</em>, not dots, at their 22px size. Each instance mints unique gradient ids so several ratings on one page don't collide on the same <code>&lt;defs&gt;</code>. It's a tiny component, but it's the kind of detail that makes a storefront feel considered rather than assembled.</p>\n\n<h2>\n  \n  \n  What we deliberately didn't ship\n</h2>\n\n<p>Being explicit about the edges is part of the same discipline:</p>\n\n<ul>\n<li>\n<strong>Mobile catalog</strong> — the sidebar-to-drawer filter layout is desktop-only for now; the mobile pass is next.</li>\n<li>\n<strong>Real cart / checkout</strong> — the visual feedback above is the whole feature today.</li>\n</ul>\n\n<p>Naming these keeps the release legible: the working parts are trustworthy, and the gaps are known rather than accidental.</p>\n\n<h2>\n  \n  \n  Takeaways\n</h2>\n\n<ul>\n<li>\n<strong>Ship what's real as real, and make everything else visibly provisional.</strong> A dead control that looks live is worse than an obvious placeholder — it teaches users the UI lies.</li>\n<li>\n<strong>Put \"is this backed by data?\" in one place.</strong> When live-vs-placeholder is a single comment on a <code>return</code> (or a single <code>isDemo</code> flag), turning a feature on is a one-liner instead of an archaeology project.</li>\n<li>\n<strong>Demo data is fine — as scaffolding, not as fake inventory.</strong> Fillers that exercise sorting and pagination are useful; fillers that route to product pages that don't exist are a trap. One flag keeps the two honest.</li>\n<li>\n<strong>Optimistic UI is honest when it admits what it is.</strong> \"Added to cart\" with no cart is fine <em>if</em> it does exactly that and nothing more.</li>\n</ul>\n\n<p>A full catalog and PDP, shipped on top of a data model that's still filling in — not by faking the missing half, but by drawing a clear line between what's real and what's coming.</p>\n\n\n\n\n<h2>\n  \n  \n  About the author\n</h2>\n\n<p>I'm <strong>Alex</strong> — a DevOps engineer building <a href=\"https://brewly.online\" rel=\"noopener noreferrer\"><strong>Brewly Store</strong></a>, a coffee e-commerce platform that runs entirely on Cloudflare's edge (Workers, D1, Pages). I write about edge architecture, shipping discipline, and the debugging stories that come with production.</p>\n\n<ul>\n<li>🌐 Live project: <strong><a href=\"https://brewly.online\" rel=\"noopener noreferrer\">brewly.online</a></strong>\n</li>\n<li>🎥 YouTube — building the Brewly Telegram bot on Cloudflare Workers: <a href=\"https://www.youtube.com/watch?v=Wm5E8TSZRhw\" rel=\"noopener noreferrer\">watch the walkthrough</a>\n</li>\n<li>💼 LinkedIn: <a href=\"https://www.linkedin.com/in/alex-d-732900177/\" rel=\"noopener noreferrer\">alex-d</a>\n</li>\n<li>🐦 X: <a href=\"https://x.com/mainoceanm\" rel=\"noopener noreferrer\">@mainoceanm</a>\n</li>\n<li>✉️ <a href=\"mailto:mainoceanm@gmail.com\">mainoceanm@gmail.com</a>\n</li>\n</ul>\n\n<p><em>How do you handle shipping UI ahead of the backend? Tell me in the comments.</em></p>","score":3},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"Supercharge Your Mobile Development with HY-APP: A Modern Vue 3 & TypeScript UI Component Library","link":"https://dev.to/hy-app/supercharge-your-mobile-development-with-hy-app-a-modern-vue-3-typescript-ui-component-library-3i66","pubDate":"Tue, 21 Jul 2026 08:54:18 +0000","description":"<p>Introduction<br>\nWhen building cross-platform mobile applications, having a reliable, high-performance, and developer-friendly UI component library is essential. If you are developing with Vue 3 and TypeScript, you want tools that offer robust type safety, reactive performance, and sleek, modern design elements out of the box.</p>\n\n<p>Enter Hy-App feature-rich mobile UI component library built specifically for Vue 3 and TypeScript.</p>\n\n<p>Why Choose Hy-App?<br>\nBuilt for Vue 3: Leverages the Composition API for cleaner, more maintainable, and reusable code logic.</p>\n\n<p>TypeScript Support: Delivers full type definitions out of the box, enhancing your IDE's autocomplete and catching errors early in development.</p>\n\n<p>Mobile-Optimized: Designed with performance and touch-interaction in mind, ensuring smooth animations and transitions on mobile viewports.</p>\n\n<p>Developer Friendly: Simple integration and comprehensive components help you drastically reduce boilerplate code and speed up time-to-market.</p>\n\n<p>Core Features at a Glance<br>\nRich Component Ecosystem: Includes essential mobile navigation bars, buttons, list views, popups, form inputs, and feedback components.</p>\n\n<p>Modern Aesthetics: Clean, minimalist UI styles that fit contemporary design standards and can be easily customized.</p>\n\n<p>Seamless Cross-Platform Scaling: Tailored to perform seamlessly in mobile web and multi-platform deployment environments.</p>\n\n<p>Getting Started<br>\nTo explore the official documentation, component playgrounds, installation guides, and interactive code examples, check out the official website:</p>\n\n<p>👉 <a href=\"https://www.hy-design-uni.top/\" rel=\"noopener noreferrer\">https://www.hy-design-uni.top/</a></p>\n\n<p>Conclusion<br>\nWhether you are spinning up a brand-new MVP or scaling an existing enterprise mobile app, Hy-App streamlines your workflow by combining the cutting-edge power of Vue 3 and TypeScript with production-ready mobile components.</p>\n\n<p>Head over to the official website today to dive into the docs and start building faster!</p>","score":3},{"source":"https://javascriptweekly.com/rss","sourceHost":"javascriptweekly.com","title":"The coding challenge you don't want to pass","link":"https://javascriptweekly.com/issues/795","pubDate":"Tue, 21 Jul 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>#​795 — July 21, 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/188234/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/188177/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/xqczohnqoev2kuyybhmk.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/188177/rss\" title=\"framework-benchmarks.as93.net\" style=\" color: #3366aa;    font-size: 1.1em; line-height: 1.4em;\">Framework Benchmarks: Compare Frontend Frameworks</a></span> — An experienced developer built and benchmarked the same app across numerous frameworks (e.g. Angular, Solid, React, Alpine.js…). Here are the results, covering bundle size, build time, UX metrics, and more.</p>\n  <p>Alicia Sykes </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>💡 Alicia also created <a href=\"https://javascriptweekly.com/link/188178/rss\" style=\" color: #3366aa; font-weight: 500;\">Stack Match</a>, a page that lets you specify your criteria for a framework and shows the best matches. Vue usually wins though!</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  <a href=\"https://javascriptweekly.com/link/188176/rss\" style=\" color: #3366aa;   \"><img src=\"https://res.cloudinary.com/cpress/image/upload/c_limit,w_480,h_480,q_auto/copm/5f351a17.png\" width=\"110\" 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/188176/rss\" title=\"xyops.io\" style=\" color: #3366aa;    font-size: 1.05em;\">xyOps: Open Source Ops Automation, from the Cronicle Team</a></span> — Run jobs across your fleet, build visual runbooks, monitor live metrics, and trigger alerts, tickets, snapshots, or follow-up actions. Self-hosted, BSD licensed, and all app features are included. Auto-import from Cronicle.</p>\n  <p>xyOps <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/188179/rss\" title=\"nuxt.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Nuxt 4.5 Released: The Full-Stack Vue Framework</a></span> — Daniel Roe calls it the biggest release in a while: Vite 8, Rspack 2, experimental SSR streaming, a new <code>useLayout</code> composable, and a lot of preparation for Nuxt 5. Nuxt 3 users take note: it goes end of life July 31.</p>\n  <p>Nuxt 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><a href=\"https://javascriptweekly.com/link/188180/rss\" style=\" color: #3366aa; font-weight: 500;   \">Vue 3.6 is now in Release Candidate stage</a> – Vapor Mode, first teased over two years ago, is now feature complete and ready to go.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188181/rss\" style=\" color: #3366aa; font-weight: 500;   \">Angular is switching to a yearly release cadence</a>, with v23 scheduled for June 2027, a year after <a href=\"https://javascriptweekly.com/link/188182/rss\" style=\" color: #3366aa; font-weight: 500;   \">v22 dropped</a>.</p>\n</li>\n<li>\n<p>ESLint now offers <a href=\"https://javascriptweekly.com/link/188183/rss\" style=\" color: #3366aa; font-weight: 500;   \">official 'codemods' for automating ESLint version migrations</a>, with v8→v9 and v9→v10 available.</p>\n</li>\n<li>\n<p>🇫🇷 <a href=\"https://javascriptweekly.com/link/188184/rss\" style=\" color: #3366aa; font-weight: 500;   \">An official Three.js conference</a> is coming to Paris, France, this September 10-11. <a href=\"https://javascriptweekly.com/link/188185/rss\" style=\" color: #3366aa; font-weight: 500;   \">Three.js</a> developer <a href=\"https://javascriptweekly.com/link/188186/rss\" style=\" color: #3366aa; font-weight: 500;   \">Mr.doob</a> will, of course, be there.</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/188187/rss\" style=\" color: #3366aa; font-weight: 500;   \">Preact 11.0 Beta 2</a> – <em>\"One of the last betas for Preact 11,\"</em> says maintainer Jovi De Croock. There's already <a href=\"https://javascriptweekly.com/link/188188/rss\" style=\" color: #3366aa; font-weight: 500;   \">a migration guide</a> ready to use.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188189/rss\" style=\" color: #3366aa; font-weight: 500;   \">WebStorm 2026.2</a> – JetBrains' JavaScript IDE adds TypeScript 7 support, and enhances support for Svelte and Vue.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188190/rss\" style=\" color: #3366aa; font-weight: 500;   \">Astro 7.1</a>, <a href=\"https://javascriptweekly.com/link/188191/rss\" style=\" color: #3366aa; font-weight: 500;   \">Rslint 0.7</a>, <a href=\"https://javascriptweekly.com/link/188192/rss\" style=\" color: #3366aa; font-weight: 500;   \">Rolldown 1.2</a>, <a href=\"https://javascriptweekly.com/link/188193/rss\" style=\" color: #3366aa; font-weight: 500;   \"><code>actions/setup-node@v7</code></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<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/188194/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/y1sie2zdhnhjtqtnvwcy.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/188194/rss\" title=\"www.elastic.co\" style=\" color: #3366aa;    font-size: 1.05em;\">How a Fake Interview's Coding Challenge Steals Credentials</a></span> — A dissection of a North Korean campaign that hides malware inside SVG images in a fake job interview's JavaScript 'coding challenge'. The targeting of job-seeking devs is on the increase, as Roman Imankulov <a href=\"https://javascriptweekly.com/link/188195/rss\" style=\" color: #3366aa;   \">recently discovered first-hand</a>.</p>\n  <p>Daniel Stepanic (Elastic) </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/188196/rss\" title=\"www.miris.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Creating Your First Streamed 3D Asset in Under 10 Minutes</a></span> — Generate a streamable 3D asset, preview it in-browser, and share it. No code, no viewer downloads.</p>\n  <p>Miris <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/188197/rss\" title=\"tech.olx.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Handling Concurrency on the Web with Web Locks API</a></span> — The <a href=\"https://javascriptweekly.com/link/188198/rss\" style=\" color: #3366aa;   \">Web Locks API</a> is a widely available browser API to allow tabs or workers with the same origin to hold and release locks.</p>\n  <p>Cesar Contreras </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/188199/rss\" title=\"www.arshad.fyi\" style=\" color: #3366aa;    font-size: 1.05em;\">Engineering High-Performance Parsers with Data-Oriented Design</a></span> — A look into how data-oriented design helped build <a href=\"https://javascriptweekly.com/link/188200/rss\" style=\" color: #3366aa;   \">Yuku</a>, a JavaScript parser that's <a href=\"https://javascriptweekly.com/link/188201/rss\" style=\" color: #3366aa;   \">faster</a> than alternatives like Oxc, SWC or Babel (you can <a href=\"https://javascriptweekly.com/link/188202/rss\" style=\" color: #3366aa;   \">give it a spin here</a>).</p>\n  <p>Arshad Yaseen </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 article is largely about building a parser in Zig but the underlying <a href=\"https://javascriptweekly.com/link/188200/rss\" style=\" color: #3366aa; font-weight: 500;\">Yuku</a> and <a href=\"https://javascriptweekly.com/link/188203/rss\" style=\" color: #3366aa; font-weight: 500;\">Yuku Analyzer</a> projects are worth checking out.</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<p>📄 <a href=\"https://javascriptweekly.com/link/188204/rss\" style=\" color: #3366aa; font-weight: 500;   \">HTML in Canvas: Rendering Real DOM Inside &lt;canvas&gt;</a>  <cite>Agustin Barrientos</cite></p>\n<p>📺 <a href=\"https://javascriptweekly.com/link/188205/rss\" style=\" color: #3366aa; font-weight: 500;   \">The Framework Wars are Over: Why No One Dethroned React</a> – Kent's take in 11 minutes. <cite>Kent C. Dodds</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/188206/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/hd77f7jirzohktslvrxp.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/188206/rss\" title=\"microsoft.github.io\" style=\" color: #3366aa;    font-size: 1.05em;\">Flint: Chart Specs that Compile to Vega-Lite, ECharts, or Chart.js</a></span> — A Microsoft project that compiles a simple, declarative JSON-based spec of a data visualization into a form that Vega-Lite, ECharts or Chart.js can render. It's pitched at agentic use, but is a simple intermediate format humans could benefit from too.</p>\n  <p>Microsoft Research </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/188207/rss\" title=\"www.tigerdata.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Query Live Data. Skip the Analytics Pipeline</a></span> — TimescaleDB extends Postgres for real-time analytics at scale. No second system, no pipeline. <a href=\"https://javascriptweekly.com/link/188207/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/188208/rss\" title=\"github.com\" style=\" color: #3366aa;    font-size: 1.05em;\">unwasm: Import .wasm Files Like Any Other ES Module</a></span> — Build-time tooling for when WASM is part of a bundled app and you want it to feel more like a normal module (e.g. <code>import { sum } from \"sum.wasm\"</code>).</p>\n  <p>unjs </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/188209/rss\" title=\"botkit.fedify.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">BotKit: Build Standalone ActivityPub Bots</a></span> — A way to run a bot (or multiple bots) that Mastodon users can see and follow but without needing an account anywhere. Write it all in JavaScript and self-host with Deno, Node.js or even Cloudflare Workers.</p>\n  <p>Fedify </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/188210/rss\" title=\"colorjs.io\" style=\" color: #3366aa;    font-size: 1.05em;\">Color.js 0.7: 'Let’s Get Serious About Color'</a></span> — A fantastic library for working with colors, following the latest specs. <a href=\"https://javascriptweekly.com/link/188211/rss\" style=\" color: #3366aa;   \">v0.7</a> is a big update with support for new color spaces, a new gamut mapping method, and a smarter <code>display()</code> fallback for colors not natively supported by a browser.</p>\n  <p>Lea Verou and Chris Lilley </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/188212/rss\" style=\" color: #3366aa; font-weight: 500;   \">Travels 2.1</a> – Efficient framework-agnostic undo/redo library based on JSON patches rather than snapshots.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188213/rss\" style=\" color: #3366aa; font-weight: 500;   \">gridstack.js 13.0</a> – Mature library for creating responsive dashboard layouts with drag-and-drop support.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188214/rss\" style=\" color: #3366aa; font-weight: 500;   \">np 12.0</a> – Sindre Sorhus's \"better <code>npm publish</code>\" adds support for npm 12 and staged publishing.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188215/rss\" style=\" color: #3366aa; font-weight: 500;   \">AngularEditor 3.1</a> – Simple WYSIWYG rich text editor for Angular 22+. (<a href=\"https://javascriptweekly.com/link/188216/rss\" style=\" color: #3366aa; font-weight: 500;   \">Demo</a>)</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188217/rss\" style=\" color: #3366aa; font-weight: 500;   \">Pinia 4.0</a> – Vue's state-management library gets leaner and goes ESM-only.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188218/rss\" style=\" color: #3366aa; font-weight: 500;   \">Bolt 5.0</a> – Slack's official framework for building Slack apps.</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>🤖 If you can code, you can build a robot. Viam 101 is a free 90-min course — no hardware needed. <a href=\"https://javascriptweekly.com/link/188221/rss\" style=\" color: #3366aa; font-weight: 500;   \">Sign up by July 31 to win a $3,500 arm</a>.</p>\n \n<p>Flaky tests slowing down dev? <a href=\"https://javascriptweekly.com/link/188219/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>Drowning in AI dev tools? You really only need these 3 tools to ship mobile apps: skills, MCP, and your workflow. <a href=\"https://javascriptweekly.com/link/188220/rss\" style=\" color: #3366aa; font-weight: 500;   \">Read the guide</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/188222/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/p3bbkhfkz0skzmdw1wmj.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>Famously, the only thing Java gave JavaScript was the first four letters of its name. That goes unmentioned in <a href=\"https://javascriptweekly.com/link/188222/rss\" style=\" color: #3366aa; font-weight: 500;   \">▶️ this new documentary about Java</a> (74 minutes), but I enjoyed learning Java's story anyway. It's from the same folks as the fantastic <a href=\"https://javascriptweekly.com/link/188223/rss\" style=\" color: #3366aa; font-weight: 500;   \">Vite</a>, <a href=\"https://javascriptweekly.com/link/188224/rss\" style=\" color: #3366aa; font-weight: 500;   \">Angular</a>, and <a href=\"https://javascriptweekly.com/link/188225/rss\" style=\" color: #3366aa; font-weight: 500;   \">Node.js</a> documentaries.</p>\n</li>\n<li>\n<p>🤖 <a href=\"https://javascriptweekly.com/link/188226/rss\" style=\" color: #3366aa; font-weight: 500;   \">ReactBench</a> is a new coding agent evaluation based around models' abilities to build working, realistic React apps. GPT 5.6 leads the way for now.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/188227/rss\" style=\" color: #3366aa; font-weight: 500;   \">shadcn/typeset</a> is a typography system for rendered HTML that makes font styling and spacing simpler to configure.</p>\n</li>\n<li>\n<p>Cloudflare has entirely rebuilt <a href=\"https://javascriptweekly.com/link/188228/rss\" style=\" color: #3366aa; font-weight: 500;   \">its blog</a> on top of <a href=\"https://javascriptweekly.com/link/188229/rss\" style=\" color: #3366aa; font-weight: 500;   \">EmDash</a>, the <a href=\"https://javascriptweekly.com/link/188230/rss\" style=\" color: #3366aa; font-weight: 500;   \">Astro</a>-powered 'spiritual successor to WordPress' it unveiled (genuinely) on April 1.</p>\n</li>\n<li>\n<p>🕹️ If you want to take part in <a href=\"https://javascriptweekly.com/link/188231/rss\" style=\" color: #3366aa; font-weight: 500;   \">the next js13kGames</a> game dev competition, the theme will be announced on August 13 with submissions due September 13.</p>\n</li>\n<li>\n<p>✨ An updated list of the <a href=\"https://javascriptweekly.com/link/188232/rss\" style=\" color: #3366aa; font-weight: 500;   \">top 100 starred JavaScript projects</a> on GitHub.</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/795/rss\" width=\"1\" height=\"1\" />","score":2},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"$100 million for open source: A milestone built by the community","link":"https://github.blog/open-source/maintainers/100-million-for-open-source-a-milestone-built-by-the-community/","pubDate":"Mon, 20 Jul 2026 16:00:00 +0000","description":"<p>Celebrating $100 million contributed by the community to the people who build and sustain open source every day.</p>\n<p>The post <a href=\"https://github.blog/open-source/maintainers/100-million-for-open-source-a-milestone-built-by-the-community/\">$100 million for open source: A milestone built by the community</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"Why Your JavaScript Runs in the Wrong Order","link":"https://dev.to/mrajaeim/why-your-javascript-runs-in-the-wrong-order-2fki","pubDate":"Sun, 19 Jul 2026 16:30:00 +0000","description":"<p>Copy this into your browser console:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">1</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"nf\">setTimeout</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2</span><span class=\"dl\">'</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">);</span>\n<span class=\"nb\">Promise</span><span class=\"p\">.</span><span class=\"nf\">resolve</span><span class=\"p\">().</span><span class=\"nf\">then</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">3</span><span class=\"dl\">'</span><span class=\"p\">));</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">4</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>You probably expect: <code>1</code>, <code>2</code>, <code>3</code>, <code>4</code>.</p>\n\n<p>You get: <code>1</code>, <code>4</code>, <code>3</code>, <code>2</code>.</p>\n\n<p>Nothing is broken. JavaScript is doing exactly what it was designed to do. The confusing part is that the language <em>looks</em> like it runs top to bottom, but it does not always work that way.</p>\n\n<p>In this article you will learn three ideas that explain almost every async surprise in JavaScript:</p>\n\n<ol>\n<li>The <strong>call stack</strong> (what runs right now)</li>\n<li>The <strong>event loop</strong> (what runs next)</li>\n<li>\n<strong>Async code</strong> (how waiting fits in without freezing your app)</li>\n</ol>\n\n<p>No prior knowledge of the event loop required. We will build the mental model step by step.</p>\n\n\n\n\n<h2>\n  \n  \n  The Problem\n</h2>\n\n<h3>\n  \n  \n  What developers expect\n</h3>\n\n<p>Most of us learn JavaScript like this: the engine reads your file line by line and runs each line in order.</p>\n\n<p>That mental model works fine for code like this:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">function</span> <span class=\"nf\">greet</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">hello</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"nf\">greet</span><span class=\"p\">();</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">bye</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Output: <code>hello</code>, then <code>bye</code>. Easy.</p>\n\n<p>Then we add <code>setTimeout</code>, Promises, and <code>async/await</code>. The same mental model breaks.</p>\n\n<h3>\n  \n  \n  A common frustrating moment\n</h3>\n\n<p>You are debugging a feature. You sprinkle <code>console.log</code> calls to trace the flow. The logs come back in an order that makes no sense.</p>\n\n<p>Or you build a search box. Each keystroke fires a fetch. Sometimes an older response overwrites a newer one. The bug feels random.</p>\n\n<p>Or you use <code>async/await</code> thinking the UI will stay responsive, but a heavy loop after an <code>await</code> still freezes the page.</p>\n\n<p>These are not separate mysteries. They all come from the same root cause: <strong>JavaScript can only run one piece of code at a time, but it still needs to handle waiting.</strong></p>\n\n\n\n\n<h2>\n  \n  \n  Why It Happens\n</h2>\n\n<h3>\n  \n  \n  Part 1: The call stack\n</h3>\n\n<p>Think of the call stack as JavaScript's to-do list for <em>right now</em>. It is a Last-In, First-Out (LIFO) structure: the newest call sits on top and is the only thing that runs.</p>\n\n<p>When your code calls a function, that function goes on top of the stack. When the function finishes, it comes off. The engine always works on whatever is on top.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">function</span> <span class=\"nf\">c</span><span class=\"p\">()</span> <span class=\"p\">{</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">c</span><span class=\"dl\">'</span><span class=\"p\">);</span> <span class=\"p\">}</span>\n<span class=\"kd\">function</span> <span class=\"nf\">b</span><span class=\"p\">()</span> <span class=\"p\">{</span> <span class=\"nf\">c</span><span class=\"p\">();</span> <span class=\"p\">}</span>\n<span class=\"kd\">function</span> <span class=\"nf\">a</span><span class=\"p\">()</span> <span class=\"p\">{</span> <span class=\"nf\">b</span><span class=\"p\">();</span> <span class=\"p\">}</span>\n\n<span class=\"nf\">a</span><span class=\"p\">();</span>\n</code></pre>\n\n</div>\n\n\n\n<p>While <code>c</code> runs, the stack looks like this:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>┌─────────┐\n│   c()   │  ← running now\n├─────────┤\n│   b()   │\n├─────────┤\n│   a()   │\n└─────────┘\n  CALL STACK\n</code></pre>\n\n</div>\n\n\n\n<p>Rules of the call stack:</p>\n\n<ul>\n<li>Only one function runs at a time</li>\n<li>Synchronous code runs immediately, in call order</li>\n<li>When the stack is empty, the current \"turn\" of synchronous work is done</li>\n</ul>\n\n<p>That last rule is the key to everything that follows.</p>\n\n<h3>\n  \n  \n  Part 2: JavaScript cannot wait on the stack\n</h3>\n\n<p>Say you write this:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const</span> <span class=\"nx\">data</span> <span class=\"o\">=</span> <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/api/user</span><span class=\"dl\">'</span><span class=\"p\">);</span> <span class=\"c1\">// network request</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"nx\">data</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>If JavaScript actually <em>stopped and waited</em> for the network, your entire page would freeze. No clicks. No scrolling. No animations.</p>\n\n<p>So the engine does something smarter. It <strong>starts</strong> the fetch, then <strong>moves on</strong> to the next line. When the network responds later, a callback runs.</p>\n\n<p>That \"run later\" part is handled outside the call stack, by the <strong>event loop</strong>.</p>\n\n<h3>\n  \n  \n  Part 3: The event loop in plain English\n</h3>\n\n<p>Part 1 showed what runs <strong>right now</strong> (the call stack).</p>\n\n<p>Part 2 showed that <code>fetch</code> and <code>setTimeout</code> cannot block the stack while they wait.</p>\n\n<p>So where does that waiting work go? And when does the \"run later\" code actually execute?</p>\n\n<p>That is what the <strong>event loop</strong> handles. It is the coordinator that lets single-threaded JavaScript stay non-blocking: slow work (network, timers, I/O) is handed to the browser or Node.js, and the loop decides when their callbacks get a turn on the call stack.</p>\n\n<h4>\n  \n  \n  The pieces that work together\n</h4>\n\n<p>Four structures share the job. Think of them as one runtime, not four separate engines:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>+-------------------------------------------------------------+\n|                     Browser / Node.js                       |\n|                                                             |\n|   +-------------------+             +-------------------+   |\n|   |    Call Stack     |             |  Web / Node APIs  |   |\n|   |  (synchronous)    |             | (timers, fetch…)  |   |\n|   +---------+---------+             +---------+---------+   |\n|             ^                                 |             |\n|             |  pushes work                    | finishes    |\n|             |  onto the stack                 v             |\n|     +-------+-------+               +---------+---------+   |\n|     |  Event Loop   |               |  Microtask Queue  |   |\n|     | (coordinator) |&lt;--------------+ (Promises, await) |   |\n|     +---------------+   drains      +---------+---------+   |\n|                             before            ^             |\n|                             the next          |             |\n|                                     +---------+---------+   |\n|                                     |  Macrotask Queue  |   |\n|                                     | (setTimeout, DOM) |   |\n|                                     +-------------------+   |\n+-------------------------------------------------------------+\n</code></pre>\n\n</div>\n\n\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Piece</th>\n<th>Role in one sentence</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Call stack</strong></td>\n<td>LIFO stack of the function that is running <em>right now</em>. Frames go on when called, off when finished.</td>\n</tr>\n<tr>\n<td><strong>Web / Node APIs</strong></td>\n<td>Work the runtime owns outside your JS thread: timers, <code>fetch</code>, DOM events, and similar I/O.</td>\n</tr>\n<tr>\n<td><strong>Microtask queue</strong></td>\n<td>High-priority FIFO line for Promise callbacks (<code>.then</code> / <code>.catch</code> / <code>.finally</code>), <code>await</code> continuations, and <code>queueMicrotask()</code>.</td>\n</tr>\n<tr>\n<td><strong>Macrotask queue</strong></td>\n<td>Lower-priority FIFO line for timer callbacks, many I/O callbacks, and UI events. Often called the task / callback queue.</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>The event loop itself is not a background thread. It is the rulebook that moves work between these pieces.</p>\n\n<h4>\n  \n  \n  A simple analogy\n</h4>\n\n<p>Imagine a cashier (the call stack) who can only help one customer at a time.</p>\n\n<ul>\n<li>\n<strong>Synchronous code</strong> walks up and gets served immediately.</li>\n<li>\n<strong><code>setTimeout</code> and <code>fetch</code></strong> are like customers who step aside and wait for a text message (\"your order is ready\") — that waiting happens in the Web / Node APIs.</li>\n<li>When the cashier has no one in line, they check two waiting lists before calling the next walk-in customer.</li>\n</ul>\n\n<p>Those two waiting lists are the important part. JavaScript does not use one generic \"later\" queue. It uses <strong>two</strong>, with a strict priority order.</p>\n\n<h4>\n  \n  \n  The two waiting lists\n</h4>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Waiting list</th>\n<th>Plain name</th>\n<th>What goes here</th>\n<th>Examples</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Microtask queue</td>\n<td>the Promise line</td>\n<td>callbacks that should run soon</td>\n<td>\n<code>.then()</code>, <code>await</code> continuations, <code>queueMicrotask()</code>\n</td>\n</tr>\n<tr>\n<td>Macrotask queue</td>\n<td>the timer/event line</td>\n<td>callbacks that can wait a bit longer</td>\n<td>\n<code>setTimeout</code>, clicks, network I/O callbacks</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>Think of it like airport boarding:</p>\n\n<ul>\n<li>\n<strong>Microtasks</strong> = priority boarding (Promises go first)</li>\n<li>\n<strong>Macrotasks</strong> = general boarding (<code>setTimeout</code>, UI events)</li>\n</ul>\n\n<h4>\n  \n  \n  What the event loop does, step by step\n</h4>\n\n<p>The engine follows one continuous checklist:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>1. Run synchronous code until the call stack is empty\n2. Hand async APIs (setTimeout, fetch, …) to Web / Node APIs and keep going\n3. When background work finishes, drop its callback into the microtask\n   or macrotask queue (depending on the API)\n4. With an empty stack, drain EVERY microtask — including ones spawned\n   by other microtasks\n5. Take exactly ONE macrotask, push it onto the call stack, and run it\n6. Go back to step 4\n</code></pre>\n\n</div>\n\n\n\n<p>One sentence version: <strong>sync first, then all Promises, then one timer/event, then repeat.</strong></p>\n\n<p>That \"every microtask\" step matters. If a Promise callback schedules another Promise callback, the new one still runs before any macrotask. A busy microtask chain can delay timers and UI events.</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%2Fk35ljx55fb658rw3qy1h.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%2Fk35ljx55fb658rw3qy1h.png\" alt=\"call stack, event loop, and async code in JavaScript\" width=\"732\" height=\"535\"></a></p>\n\n<h4>\n  \n  \n  Where <code>setTimeout</code> and <code>fetch</code> fit\n</h4>\n\n<p>When you call <code>setTimeout</code> or <code>fetch</code>, the engine does not run your callback immediately. It hands the waiting job to the browser (or Node.js). That happens outside the call stack.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>Your JS code                    Browser / Node (outside the stack)\n─────────────                   ──────────────────────────────────\nsetTimeout(fn, 0)    ────────►   timer starts...\nfetch('/api')        ────────►   network request starts...\n                                 (your code keeps running)\n                                 ...time passes...\ntimer fires          ◄────────   puts fn in the timer/event line\nresponse arrives     ◄────────   puts .then callback in the Promise line\n</code></pre>\n\n</div>\n\n\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%2Fpd3q4rfjzww0jenvmkkh.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%2Fpd3q4rfjzww0jenvmkkh.png\" alt=\"Browser / Node (outside the stack)\" width=\"800\" height=\"770\"></a></p>\n\n<p>Your callback only runs when the event loop picks it from a waiting list and puts it back on the call stack.</p>\n\n<h4>\n  \n  \n  Walk through our opening example\n</h4>\n\n<p>Here is the snippet again:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">1</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"nf\">setTimeout</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2</span><span class=\"dl\">'</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">);</span>\n<span class=\"nb\">Promise</span><span class=\"p\">.</span><span class=\"nf\">resolve</span><span class=\"p\">().</span><span class=\"nf\">then</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">3</span><span class=\"dl\">'</span><span class=\"p\">));</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">4</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>Step 1: synchronous code (call stack)</strong></p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Line</th>\n<th>What happens</th>\n<th>Printed so far</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>console.log('1')</code></td>\n<td>runs immediately</td>\n<td><code>1</code></td>\n</tr>\n<tr>\n<td><code>setTimeout(...)</code></td>\n<td>browser starts a timer; when it fires, the callback waits in the <strong>timer/event line</strong>\n</td>\n<td><code>1</code></td>\n</tr>\n<tr>\n<td><code>Promise.then(...)</code></td>\n<td>callback waits in the <strong>Promise line</strong>\n</td>\n<td><code>1</code></td>\n</tr>\n<tr>\n<td><code>console.log('4')</code></td>\n<td>runs immediately</td>\n<td>\n<code>1</code>, <code>4</code>\n</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>Call stack is now empty. Synchronous work is done.</p>\n\n<p><strong>Step 2: Promise line (microtasks)</strong></p>\n\n<p>The engine drains the microtask queue completely before touching timers:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>What runs</th>\n<th>Printed so far</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>console.log('3')</code></td>\n<td>\n<code>1</code>, <code>4</code>, <code>3</code>\n</td>\n</tr>\n</tbody>\n</table></div>\n\n<p><strong>Step 3: timer/event line (one macrotask)</strong></p>\n\n<p>Now the loop takes exactly one macrotask — the <code>setTimeout</code> callback:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>What runs</th>\n<th>Printed so far</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>console.log('2')</code></td>\n<td>\n<code>1</code>, <code>4</code>, <code>3</code>, <code>2</code>\n</td>\n</tr>\n</tbody>\n</table></div>\n\n<p><strong>Final answer:</strong> <code>1</code>, <code>4</code>, <code>3</code>, <code>2</code></p>\n\n<p>Why that order sticks:</p>\n\n<ul>\n<li>\n<code>1</code> and <code>4</code> are pure sync work on the call stack, so they print first.</li>\n<li>\n<code>setTimeout(..., 0)</code> registers a timer right away; when it expires, the callback lands in the <strong>macrotask</strong> queue — not on the stack.</li>\n<li>The resolved Promise schedules its callback in the <strong>microtask</strong> queue.</li>\n<li>After the main script finishes, the event loop clears microtasks before any macrotask, so <code>3</code> prints before <code>2</code>.</li>\n</ul>\n\n<p><code>3</code> beats <code>2</code> because Promise callbacks always run before <code>setTimeout</code> callbacks, even when the timer is set to <code>0</code>.</p>\n\n<h4>\n  \n  \n  The one rule to remember\n</h4>\n\n<blockquote>\n<p>Sync code first. Then all Promises. Then one timer or event. Then repeat.</p>\n</blockquote>\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%2Fst3izq7adbcost8fb122.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%2Fst3izq7adbcost8fb122.png\" alt=\"The one rule to remember Sync code first Then all Promises Then one timer or event Then repeat\" width=\"799\" height=\"436\"></a></p>\n\n<p>If you remember that, the opening example stops feeling random.</p>\n\n<h3>\n  \n  \n  Part 4: What <code>async/await</code> actually does\n</h3>\n\n<p><code>async/await</code> is just Promise syntax. It does not pause the entire program.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">load</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">A</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n  <span class=\"k\">await</span> <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/api/user</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n  <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">B</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">1</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"nf\">load</span><span class=\"p\">();</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>What happens:</p>\n\n<ol>\n<li>\n<code>1</code> prints (sync)</li>\n<li>\n<code>load()</code> starts, <code>A</code> prints (sync)</li>\n<li>\n<code>await</code> hits the network and <strong>steps out</strong> of the function</li>\n<li>\n<code>2</code> prints (sync) because the stack is free</li>\n<li>Fetch finishes, <code>B</code> prints (microtask)</li>\n</ol>\n\n<p>Output: <code>1</code>, <code>A</code>, <code>2</code>, <code>B</code></p>\n\n<p>The code <em>after</em> <code>await</code> does not run when the network finishes inside your function like a blocking pause. It gets scheduled as a microtask for later.</p>\n\n\n\n\n<h2>\n  \n  \n  Manual Solution: Trace the Code by Hand\n</h2>\n\n<p>You do not need tools to predict small examples. Label each line, then follow the event loop rules.</p>\n\n<p>Take our opening snippet:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">1</span><span class=\"dl\">'</span><span class=\"p\">);</span>                                    <span class=\"c1\">// SYNC</span>\n<span class=\"nf\">setTimeout</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2</span><span class=\"dl\">'</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">);</span>               <span class=\"c1\">// schedules MACROTASK</span>\n<span class=\"nb\">Promise</span><span class=\"p\">.</span><span class=\"nf\">resolve</span><span class=\"p\">().</span><span class=\"nf\">then</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">3</span><span class=\"dl\">'</span><span class=\"p\">));</span>    <span class=\"c1\">// schedules MICROTASK</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">4</span><span class=\"dl\">'</span><span class=\"p\">);</span>                                    <span class=\"c1\">// SYNC</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Now walk through it.</p>\n\n<p><strong>Round 1: synchronous code</strong></p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Step</th>\n<th>Call stack</th>\n<th>Console output</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>console.log('1')</code></td>\n<td><code>[log]</code></td>\n<td><code>1</code></td>\n</tr>\n<tr>\n<td><code>setTimeout(...)</code></td>\n<td><code>[setTimeout]</code></td>\n<td>(schedules <code>2</code> for later)</td>\n</tr>\n<tr>\n<td><code>Promise.then(...)</code></td>\n<td><code>[then]</code></td>\n<td>(schedules <code>3</code> for later)</td>\n</tr>\n<tr>\n<td><code>console.log('4')</code></td>\n<td><code>[log]</code></td>\n<td><code>4</code></td>\n</tr>\n<tr>\n<td>Stack empty</td>\n<td><code>[]</code></td>\n<td></td>\n</tr>\n</tbody>\n</table></div>\n\n<p><strong>Round 2: microtasks</strong></p>\n\n<p>The Promise callback runs:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Console output</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>3</code></td>\n</tr>\n</tbody>\n</table></div>\n\n<p><strong>Round 3: one macrotask</strong></p>\n\n<p>The <code>setTimeout</code> callback runs:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Console output</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>2</code></td>\n</tr>\n</tbody>\n</table></div>\n\n<p><strong>Final output:</strong> <code>1</code>, <code>4</code>, <code>3</code>, <code>2</code></p>\n\n<p>That is the full manual process. For any small snippet:</p>\n\n<ol>\n<li>Run every synchronous line until the stack is empty</li>\n<li>Run every microtask</li>\n<li>Run one macrotask</li>\n<li>Repeat from step 2</li>\n</ol>\n\n<h3>\n  \n  \n  Verify in DevTools\n</h3>\n\n<p>Open Chrome DevTools, go to <strong>Sources</strong>, and set a breakpoint inside a <code>.then()</code> callback. When execution pauses, look at the <strong>Call Stack</strong> panel on the right. It shows exactly which functions are active.</p>\n\n<p>For log-order bugs, numbered labels help:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">[sync] user clicked</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>I use this in code reviews all the time. It is simple and it works.</p>\n\n\n\n\n<h2>\n  \n  \n  Drawbacks of the Manual Process\n</h2>\n\n<p>Tracing by hand is great for interview questions and 5-line snippets. It gets painful in real apps.</p>\n\n<ul>\n<li>React, Vue, and other frameworks schedule work you cannot see</li>\n<li>Network responses arrive in unpredictable order</li>\n<li>One file may mix <code>setTimeout</code>, Promises, and <code>await</code> in ways that are hard to track</li>\n<li>It is easy to forget the rule: <strong>all microtasks run before the next macrotask</strong>\n</li>\n</ul>\n\n<p>Knowing the rules is not the same as writing code that survives fast user input and slow networks. That is where patterns help.</p>\n\n\n\n\n<h2>\n  \n  \n  Better Solution: Three Rules for Async Code\n</h2>\n\n<p>Instead of tracing every line in your head, write code that matches how the runtime works.</p>\n\n<p><strong>Rule 1: Keep synchronous blocks short.</strong></p>\n\n<p>Long loops on the main thread block everything, even after an <code>await</code>. If work is heavy, break it into chunks or move it to a Web Worker.</p>\n\n<p><strong>Rule 2: Use <code>async/await</code> to sequence steps in one flow.</strong></p>\n\n<p>When step B needs the result of step A, <code>await</code> is the right tool. It keeps the logic readable.</p>\n\n<p><strong>Rule 3: Guard against out-of-order async results.</strong></p>\n\n<p>When the user can trigger the same async action multiple times (search, pagination, tab switches), ignore or cancel stale results.</p>\n\n<p>These three rules fix most production async bugs I have seen.</p>\n\n\n\n\n<h2>\n  \n  \n  Complete Code\n</h2>\n\n<p>Here is a before/after for a search box. This is the full example we will break down.</p>\n\n<p><strong>Before (buggy):</strong><br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">searchInput</span><span class=\"p\">.</span><span class=\"nf\">addEventListener</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">input</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"k\">async </span><span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">query</span> <span class=\"o\">=</span> <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">target</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">;</span>\n\n  <span class=\"kd\">const</span> <span class=\"nx\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"s2\">`/api/search?q=</span><span class=\"p\">${</span><span class=\"nx\">query</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">);</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">data</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">response</span><span class=\"p\">.</span><span class=\"nf\">json</span><span class=\"p\">();</span>\n\n  <span class=\"nf\">renderResults</span><span class=\"p\">(</span><span class=\"nx\">data</span><span class=\"p\">);</span> <span class=\"c1\">// may render stale data</span>\n<span class=\"p\">});</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>After (fixed):</strong><br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">let</span> <span class=\"nx\">activeController</span> <span class=\"o\">=</span> <span class=\"kc\">null</span><span class=\"p\">;</span>\n\n<span class=\"nx\">searchInput</span><span class=\"p\">.</span><span class=\"nf\">addEventListener</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">input</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"k\">async </span><span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">query</span> <span class=\"o\">=</span> <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">target</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">;</span>\n\n  <span class=\"c1\">// Cancel the previous in-flight request</span>\n  <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">activeController</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"nx\">activeController</span><span class=\"p\">.</span><span class=\"nf\">abort</span><span class=\"p\">();</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"nx\">activeController</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">AbortController</span><span class=\"p\">();</span>\n\n  <span class=\"k\">try</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=\"nf\">fetch</span><span class=\"p\">(</span>\n      <span class=\"s2\">`/api/search?q=</span><span class=\"p\">${</span><span class=\"nf\">encodeURIComponent</span><span class=\"p\">(</span><span class=\"nx\">query</span><span class=\"p\">)}</span><span class=\"s2\">`</span><span class=\"p\">,</span>\n      <span class=\"p\">{</span> <span class=\"na\">signal</span><span class=\"p\">:</span> <span class=\"nx\">activeController</span><span class=\"p\">.</span><span class=\"nx\">signal</span> <span class=\"p\">}</span>\n    <span class=\"p\">);</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">data</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">response</span><span class=\"p\">.</span><span class=\"nf\">json</span><span class=\"p\">();</span>\n    <span class=\"nf\">renderResults</span><span class=\"p\">(</span><span class=\"nx\">data</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=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">error</span><span class=\"p\">.</span><span class=\"nx\">name</span> <span class=\"o\">===</span> <span class=\"dl\">'</span><span class=\"s1\">AbortError</span><span class=\"dl\">'</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"k\">return</span><span class=\"p\">;</span> <span class=\"c1\">// a newer search started, ignore this one</span>\n    <span class=\"p\">}</span>\n    <span class=\"nf\">showError</span><span class=\"p\">(</span><span class=\"nx\">error</span><span class=\"p\">);</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">});</span>\n</code></pre>\n\n</div>\n\n\n\n\n\n\n<h2>\n  \n  \n  Breakdown\n</h2>\n\n<h3>\n  \n  \n  Why the \"before\" code breaks\n</h3>\n\n<p>Each keystroke starts a fetch. All requests run at the same time. Whichever response arrives <strong>last</strong> wins, not whichever query is <strong>current</strong>.</p>\n\n<p>If the user types <code>react</code> quickly, the response for <code>re</code> might arrive after <code>react</code>. The UI shows results for <code>re</code>. That looks like async code is \"random.\" It is not. The event loop is working fine. The code just never checks whether the result is still relevant.</p>\n\n<h3>\n  \n  \n  <code>activeController</code>\n</h3>\n\n<p>A variable that holds the current <code>AbortController</code>. Think of it as a remote control for the in-flight fetch.</p>\n\n<p>When the user types a new character, we abort the old request before starting a new one.</p>\n\n<h3>\n  \n  \n  <code>AbortController</code> and <code>signal</code>\n</h3>\n\n<p><code>fetch</code> accepts a <code>signal</code> option. When you call <code>controller.abort()</code>, the in-flight fetch rejects with an <code>AbortError</code>.</p>\n\n<p>This is better than ignoring stale results silently. It actually cancels work you no longer need.</p>\n\n<h3>\n  \n  \n  The <code>try/catch</code> block\n</h3>\n\n<p>Not every aborted request is an error worth showing. <code>AbortError</code> means \"a newer search replaced this one.\" We return early and do nothing.</p>\n\n<p>Real errors (network down, 500 response) still go to <code>showError</code>.</p>\n\n\n\n\n<h2>\n  \n  \n  Real Example: Step by Step\n</h2>\n\n<p>Let us trace the fixed search box when a user types <code>r</code>, then <code>re</code>.</p>\n\n<h3>\n  \n  \n  Keystroke 1: user types <code>r</code>\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>SYNC:  input handler runs\nSYNC:  abort() called (nothing to abort yet)\nSYNC:  fetch('/api/search?q=r') starts (network waits in background)\nSYNC:  handler returns, stack is empty\n...later...\nMICRO: await continuation runs, renderResults for \"r\"\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Keystroke 2: user types <code>re</code> (before <code>r</code> responds)\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>SYNC:  input handler runs\nSYNC:  abort() cancels the \"r\" request\nSYNC:  fetch('/api/search?q=re') starts\nSYNC:  handler returns\nMICRO: \"r\" fetch rejects with AbortError → catch block returns early\n...later...\nMICRO: \"re\" fetch resolves → renderResults for \"re\"\n</code></pre>\n\n</div>\n\n\n\n<p>The UI always shows results for the latest query. No race condition.</p>\n\n<h3>\n  \n  \n  Back to the classic log-order example\n</h3>\n\n<p>Paste this in your console and watch it run:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">1</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"nf\">setTimeout</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2</span><span class=\"dl\">'</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">);</span>\n<span class=\"nb\">Promise</span><span class=\"p\">.</span><span class=\"nf\">resolve</span><span class=\"p\">().</span><span class=\"nf\">then</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">3</span><span class=\"dl\">'</span><span class=\"p\">));</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">4</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Phase</th>\n<th>What runs</th>\n<th>Output so far</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Sync</td>\n<td>\n<code>1</code>, <code>4</code>\n</td>\n<td>\n<code>1</code>, <code>4</code>\n</td>\n</tr>\n<tr>\n<td>Microtasks</td>\n<td>Promise callback</td>\n<td>\n<code>1</code>, <code>4</code>, <code>3</code>\n</td>\n</tr>\n<tr>\n<td>Macrotask</td>\n<td>setTimeout callback</td>\n<td>\n<code>1</code>, <code>4</code>, <code>3</code>, <code>2</code>\n</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>Once you have done this two or three times, the order stops feeling random.</p>\n\n\n\n\n<h2>\n  \n  \n  Warnings and Edge Cases\n</h2>\n\n<h3>\n  \n  \n  Blocking the call stack freezes everything\n</h3>\n\n<p>JavaScript has one main thread for your page script. A long sync job — resizing a huge image in a loop, parsing a massive JSON blob, or an accidental infinite loop — never leaves the call stack. While that frame is stuck, the event loop cannot drain microtasks or macrotasks. Clicks queue up. Animations stall. The tab looks dead even though \"async\" APIs are still waiting in the background.</p>\n\n<p>That is why heavy CPU work belongs in chunks or in a Web Worker, not in a tight loop after an <code>await</code>.</p>\n\n<h3>\n  \n  \n  Timers are minimums, not appointments\n</h3>\n\n<p><code>setTimeout(fn, 1000)</code> does not promise that <code>fn</code> runs at the one-second mark. It means: after at least 1000ms, the callback <em>may</em> enter the macrotask queue. If the stack is busy, or microtasks keep spawning, the callback waits longer. Treat the delay as a floor, not a schedule.</p>\n\n<h3>\n  \n  \n  <code>setTimeout(fn, 0)</code> does not mean \"run immediately after sync code\"\n</h3>\n\n<p>It means \"schedule a macrotask.\" Promise callbacks still run first. If you need to defer work to after the current synchronous block but before timers, use <code>queueMicrotask()</code> instead.</p>\n\n<h3>\n  \n  \n  <code>await</code> does not run fetches in parallel\n</h3>\n\n<p>This is sequential (B waits for A):<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const</span> <span class=\"nx\">a</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/a</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"kd\">const</span> <span class=\"nx\">b</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/b</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>This runs both at once:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const</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\">=</span> <span class=\"k\">await</span> <span class=\"nb\">Promise</span><span class=\"p\">.</span><span class=\"nf\">all</span><span class=\"p\">([</span>\n  <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/a</span><span class=\"dl\">'</span><span class=\"p\">),</span>\n  <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/b</span><span class=\"dl\">'</span><span class=\"p\">),</span>\n<span class=\"p\">]);</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Long sync work after <code>await</code> still blocks the UI\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">heavy</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"k\">await</span> <span class=\"nf\">fetch</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/data</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n  <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">let</span> <span class=\"nx\">i</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span> <span class=\"nx\">i</span> <span class=\"o\">&lt;</span> <span class=\"mi\">1</span><span class=\"nx\">_000_000_000</span><span class=\"p\">;</span> <span class=\"nx\">i</span><span class=\"o\">++</span><span class=\"p\">)</span> <span class=\"p\">{}</span> <span class=\"c1\">// blocks clicks and scrolling</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><code>await</code> only yields <em>before</em> the loop, not during it.</p>\n\n<h3>\n  \n  \n  Node.js has one extra queue\n</h3>\n\n<p>In Node, <code>process.nextTick()</code> runs even before microtasks. Browser code rarely needs this, but Node tutorials lean on it. Node's loop is also organized into phases (timers, I/O, check, close, and so on). The browser-friendly story in this article still holds for most app code; when you debug server timers and <code>nextTick</code> ordering, read the <a href=\"https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick\" rel=\"noopener noreferrer\">Node event loop guide</a>.</p>\n\n<h3>\n  \n  \n  Frameworks add their own scheduling\n</h3>\n\n<p>React 18 batches state updates differently than React 17. If your logs inside <code>useEffect</code> do not match a bare-bones example, the framework may be batching work. Always test in your real setup.</p>\n\n\n\n\n<h2>\n  \n  \n  When to Use This Knowledge\n</h2>\n\n<p><strong>Reach for this mental model when:</strong></p>\n\n<ul>\n<li>Console logs appear in a confusing order</li>\n<li>A fetch-heavy UI shows stale data</li>\n<li>You are choosing between <code>setTimeout</code>, <code>Promise</code>, and <code>queueMicrotask</code>\n</li>\n<li>You are reviewing async code in a pull request</li>\n</ul>\n\n<p><strong>This alone is not enough when:</strong></p>\n\n<ul>\n<li>You have CPU-heavy work (use a Web Worker)</li>\n<li>You need true multi-core parallelism</li>\n<li>You are doing server-side concurrency in Node (look into worker threads)</li>\n</ul>\n\n<p><strong>On a team</strong>, it helps to agree on one pattern for canceling stale fetches (<code>AbortController</code> is a good default) and to ask in review: \"what happens if the user triggers this twice before the first call finishes?\"</p>\n\n\n\n\n<h2>\n  \n  \n  Conclusion\n</h2>\n\n<p>JavaScript runs synchronous code on the <strong>call stack</strong> first. When the stack is empty, the <strong>event loop</strong> drains every microtask (Promises, <code>await</code>), then runs one macrotask (<code>setTimeout</code>, events), and repeats.</p>\n\n<p>That single rule explains why <code>1, 4, 3, 2</code> is the correct output for our opening example. It also explains why search boxes need cancellation logic, and why <code>async/await</code> alone does not keep the UI smooth during heavy loops.</p>\n\n<p>You do not need to memorize every edge case. Remember the big picture: <strong>sync first, then microtasks, then one macrotask.</strong> Everything else is a variation on that.</p>\n\n<h3>\n  \n  \n  Go further\n</h3>\n\n<ul>\n<li>\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model\" rel=\"noopener noreferrer\">MDN: JavaScript execution model</a> — the formal picture of the stack, tasks, and microtasks</li>\n<li>\n<a href=\"https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick\" rel=\"noopener noreferrer\">Node.js: The Node.js Event Loop</a> — how phases and <code>nextTick</code> differ from the browser story</li>\n</ul>\n\n<p>If you want a follow-up deep dive, the natural next stops are: how <code>async/await</code> maps onto that visual timeline, how to hunt a specific out-of-order race, or how Node's phases differ when you leave the browser.</p>\n\n<p>What was the first async bug that made you stop and question how JavaScript actually works?</p>","score":5},{"source":"https://nuxt.com/blog/rss.xml","sourceHost":"nuxt.com","title":"Nuxt 4.5","link":"https://nuxt.com/blog/v4-5","pubDate":"Sat, 18 Jul 2026 00:00:00 GMT","description":"Nuxt 4.5 is our biggest release in a while. Vite 8, Rspack 2 powered by Rsbuild, experimental SSR streaming, a stable error code system, a new useLayout composable, named views, and a lot of groundwork for Nuxt 5.","score":5},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"The cost of saying yes has changed","link":"https://github.blog/engineering/the-cost-of-saying-yes-has-changed/","pubDate":"Fri, 17 Jul 2026 16:46:47 +0000","description":"<p>The cost of writing code dropped; the cost of owning it didn't. A framework for deciding which changes are actually cheap in the AI era.</p>\n<p>The post <a href=\"https://github.blog/engineering/the-cost-of-saying-yes-has-changed/\">The cost of saying yes has changed</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://blog.risingstack.com/rss/","sourceHost":"blog.risingstack.com","title":"GPT-Red: OpenAI Is Training Models to Break Other Models","link":"https://blog.risingstack.com/gpt-red-openai-self-improving-ai-security/","pubDate":"Thu, 16 Jul 2026 13:05:35 +0000","description":"<p>Prompt injection is still one of the least comfortable problems in AI development. You can improve your system prompt, restrict tools, validate outputs, and add approval steps before sensitive actions. Still, the model eventually has to read data you do not control. It might browse a webpage, process an email, inspect a repository, or use [&#8230;]</p>\n<p>The post <a href=\"https://blog.risingstack.com/gpt-red-openai-self-improving-ai-security/\">GPT-Red: OpenAI Is Training Models to Break Other Models</a> appeared first on <a href=\"https://blog.risingstack.com\">RisingStack Engineering</a>.</p>","score":4}]}