[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"articles-feed-\u002F-3--":3,"$fQa3xUpnRDPNiFFu2QS4K-DAjYmsapeR9fNhPNBdpkOc":-1},{"items":4,"page":407,"pageSize":408,"totalCount":409},[5,33,58,85,107,133,149,166,191,209,229,249,268,290,307,325,339,356,372,390],{"content":6,"createdAt":7,"id":8,"image":9,"isAffiliate":10,"isPublished":11,"publishedAt":12,"slug":13,"sourceId":14,"sourceName":15,"sourceType":16,"summary":17,"title":18,"updatedAt":19,"url":20,"urlHash":21,"tags":22},"Our own AI agent on nuxt.com, grounded in the official docs and the Nuxt ecosystem. We built it internally using the AI SDK, our MCP server, and Nuxt UI components.","2026-04-30T04:18:55.740Z","019ddc9c-7a6a-76a8-8ec2-038b607968be","https:\u002F\u002Fnuxt.com\u002Fnuxt-agent.jpg",false,true,"2026-04-29T00:00:00.000Z","introducing-the-nuxt-agent","019d6c1a-e19c-736e-b489-240be1c0d29a","Nuxt Blog","rss","The article introduces the Nuxt Agent, an AI tool developed for nuxt.com that utilizes the official documentation and the Nuxt ecosystem. It was created using the AI SDK, MCP server, and Nuxt UI components.","Introducing the Nuxt Agent","2026-04-30T04:19:02.005Z","https:\u002F\u002Fnuxt.com\u002Fblog\u002Fintroducing-nuxt-agent","b10cc6b9333ffa6506fbfa0656387e9b83b8ee921c06c62bb137df8da05cd754",[23,27,30],{"color":24,"id":25,"name":26,"slug":26},"#10b981","019d6bd8-fad3-70a9-a74d-ab96e3a2f45d","nuxt",{"color":24,"id":28,"name":29,"slug":29},"019d6bd8-ca8b-709c-b9a5-77d4910da162","ui-components",{"color":24,"id":31,"name":32,"slug":32},"019d9d20-e07b-7685-9ebb-fae0b963f243","ai",{"content":34,"createdAt":35,"id":36,"image":37,"isAffiliate":10,"isPublished":11,"publishedAt":38,"slug":39,"sourceId":40,"sourceName":41,"sourceType":42,"summary":43,"title":44,"updatedAt":45,"url":46,"urlHash":47,"tags":48},"Modern Frontend Engineering: Tools, Tradeoffs, and the AI Shift. It will focus on software quality in the era of AI with the Creator of ...","2026-04-28T12:00:01.729Z","019dd3f5-e8ad-708a-8f6e-4c164bd16e7c","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FL5aJVG5g7k0\u002Fhqdefault.jpg","2026-04-28T10:00:06.000Z","jakub-andrzejewski-evan-you---panel-beyond-the-vibe-code-quality-first","019d9ce0-e8f2-774a-ba57-a61c19469fe1","Vuejs Amsterdam","youtube","The panel featuring Jakub Andrzejewski and Evan You discusses the importance of code quality in modern frontend engineering, particularly in the context of AI advancements. They explore various tools and tradeoffs that developers face today.","Jakub Andrzejewski, Evan You - Panel: Beyond The Vibe: Code Quality First","2026-04-28T12:00:12.454Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=L5aJVG5g7k0","eaffed00699dc730765b58457c919e290cc6cef62eeb40ad522499d42448b87f",[49,52,55],{"color":24,"id":50,"name":51,"slug":51},"019d6bd8-c9a8-7783-bd22-03145b355427","vue",{"color":24,"id":53,"name":54,"slug":54},"019d6bd8-ca26-775c-b9b5-c3439dbe5789","performance",{"color":24,"id":56,"name":57,"slug":57},"019d9cf0-ca04-75bb-ad86-ce8da1c0be23","architecture",{"content":59,"createdAt":60,"id":61,"image":62,"isAffiliate":11,"isPublished":11,"publishedAt":63,"slug":64,"sourceId":65,"sourceName":66,"sourceType":67,"summary":68,"title":69,"updatedAt":70,"url":71,"urlHash":72,"tags":73},"I first started this article with the intention of teaching you how to combine Nuxt UI's Editor component with Jazz's collaborative rich text model to build a real-time shared editing experience in Nuxt. But then I realized that I was using AI to do a lot of the heavy lifting for me. Given the day and age we live in, you'd likely use AI to do the same thing. So instead of walking you through the text step by step, I'm going to take a different approach. Here's the plan: I'll start by explaining why I wanted to build this in the first place. Next I'll show off the final result and show you how you can use it for yourself if you'd like. Then I'll give you a list of takeaways on building with AI that: I learned during the process I've learned in the past but came up during this build Why I Thought Building the Nuxt UI Collaborative Editor Was a Good Idea Sure there are some off the shelf solutions for rich text editing with comments, but I'm pretty married to the Nuxt ecosystem. So I said, &quot;why not!? Let's give it a go!&quot; Furthermore, the control over the source code means I can also extend it in ways that I couldn't do with a pre-built solution. A Nuxt UI Collaborative Editor with Comment Support: the Final Result So what exactly did I build? Here's what's included in the final result: A Nuxt UI Editor component with a toolbar that handles the edit and display of rich text Support for multiple users to edit the same document together in real-time (courtesy of Jazz.) Support for comments tied to specific highlighted text in the editor Image upload, sizing, and alignment support A editing optimized layout that keeps the editor front and center while showing comments conditionally in a sidebar. The ability to add replies to comments The ability to mark comments as &quot;resolved&quot; 👉 You can demo the editor for yourself here How to Use the Nuxt UI Collaborative Editor with Comment Support If you want to use it for yourself, you can clone the demo repository, get an API key for Jazz at jazz.tools, and run it locally: git clone https:\u002F\u002Fgithub.com\u002Fdanielkellyio\u002Fnuxt-jazz-collaborative-editor-with-comments.git cd nuxt-jazz-collaborative-editor-with-comments npm install npm run dev Feel free to use it as a starting point for your own projects or copying and pasting the code existing codebases. A List of Takeaways on Building with AI While AI is a great tool, it's not 100% intuitive on how to use it to your advantage. unlearn.dev is a great resource for mastering the art of wielding it effectively. It's a full collection of workflows and strategies. Until then, here's a short list of takeaways on building with AI specfic to this project. Choosing the right tools for the job goes a long way. What do I mean? Well, I've never used Jazz before but given the exact use case it's meant to solve (multi-user real-time data syncing), it was a great fit for the job. (Thanks Alexander Opalic for turning me on to it!). The library's llms.txt was easy to pass on to my AI agent. I'm often tempted to reach for built-in REST api endpoints via Nitro, but it's worth exlporing new depenedencies when a better fit is available. Telling the Agent to iterate using the browser The editor setup was super simple, it's just the Nuxt UI editor component with it's companion Toolbar. Making it collaborative was also extremely easy with Jazz. However, things started getting a little harrier when I attempted to add comments support. The AI agent wanted to take the easiest path even though it didn't result in the best UI\u002FUX. So I told it: &quot;Iterate until it's a notion style commenting UI. localhost:3000 Use the browser.&quot; This forced it to: Do more of the work autonomously while I worked on other things Gave it tools to check it's own work And gave it a clearer goal without a lot of extra context creation on my part That leads me to... Mention existing projects that are similar to the one you're building Just like I mentioned &quot;Notion&quot;, if there's another popular solution similar to what you're attempting, defitintely mention it. The process let's you get more thoughts out of your head and into the context without having to spell it all out. Do note of course this only works when the tool you're mentioning are big enough that the model knows a thing or 2 about them. Screenshots of lesser known tools go a long way too! Work with different agents at the same time on non-overlapping tasks Yes, I know that we can work with multiple agents with worktrees or in the cloud on different branches, and so on. But it can still be more back and forth than necessary and some merge conflicts that take time to resolve. To help me keep my focus, while not just sipping on coffee and waiting for the agent to finish, I'll often work with different agents at the same time on non-overlapping tasks. These are tasks that both relate to the current objective and exist in the same branch but I know won't touch the same lines of code. For example, during this build, I worked with one agent on the image upload support while simultaneously working on some of the comment UI that I knew was pretty isolated. I've also found this helpful when building homepages or landing pages with multiple sections. Each section will get it's own component and I can jump back and forth between agents on each section to dial them in without worrying about conflicts. Look for opportunities for smart abstraction It didn't take looking at the code to know that Jazz isn't purpose built for use with Vue. It has Svelte docs and React docs but non for my favorite framework. So experience told me that my code would be DRY-er and more maintainable if one of the first tasks I undertook was to create a reactive wrapper around the Jazz API. So after the initial documention collaboration was working, I instructed the agent: &quot;could we make a reusable composable for wrapping jazz models with Vue reactive state?&quot; Of course, it was very obliging and created a useJazzReactiveState composable that ended up being used for both comments and document title syncing. Asking the agent to commit along the way Sometimes I'm happy with the state of the code but I'm working in a view that doesn't give me easy access to the terminal (for example, cursor's new Agent Window—which is awesome by the way!). So instead of changing context, I'll simply ask the agent to commit the changes in the same breath I tell it to continue working on the next new task. It's an easy thing but a quick way to stay in the flow. I did this on many occasions throughout this build. Don't be afraid to abort and start over At one point, I attempted to add supprt for viewing different users cursors (like in Notion or Google docs). The agent got a great rough draft of it, but it was too buggy and added complexity that just wasn't worth it. So I simply aborted the task and moved on to another. Ask your agent to &quot;simplify&quot; the implementation When first implementing the comments, it randomly decided to use one data structure for the Jazz models and then another when displaying the comments to the UI requiring a transform between the two. I simply asked it to &quot;simplify&quot; the implementation removed the extra transformation step. Combine multiple stratgies for the best results At one point the AI agent just failed to get the &quot;resolve&quot; feature for comments correctly so I I stated: &quot;now it's not persisting to Jazz appropriately. Simplify and make work. Use the browser to iterate so that: Resolving a comment immediately triggers the proper UI updates Re-opening a component does the opposite All this saves with Jazz&quot; Sometimes it's helpful for it to just take a step back and try a different approach with clear objectives in mind and the right direction for how to test it's steps. Conclusion Building with AI is a lot of fun and it's a great way to get things done quickly. It's by no means a magic bullet but a project like this would have taken me days if not weeks to build before. Now it only took 3-4 hours (and that's with writing this article too!).","2026-04-30T04:18:57.269Z","019ddc9c-805c-71c8-81be-4ae362cfeb7c","https:\u002F\u002Fblog.vueschool.io\u002Fwp-content\u002Fuploads\u002F2026\u002F04\u002Fcollaborative-editor-example.jpg","2026-04-28T00:00:34.000Z","vibe-coding-a-collaborative-editor-with-comment-support-with-nuxt-ui-and-jazz","019d6bd5-87de-77ef-ac92-98aa56cda920","VueSchool","vueschool","This article explores the creation of a collaborative editor using Nuxt UI and Jazz, focusing on real-time editing and comment support. It discusses the final product's features, including a rich text editor with a toolbar, image upload capabilities, and a layout optimized for collaboration. The author also shares insights on leveraging AI in the development process.","Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz","2026-04-30T04:19:01.987Z","https:\u002F\u002Fblog.vueschool.io\u002Fvuejs-tutorials\u002Fvibe-coding-a-collaborative-editor-with-comment-support-with-nuxt-ui-and-jazz\u002F?friend=MOKKAPPS","4b0b425ef41c41adbed9d267524c9d8cedcbd7605a41940a149627d2e550ed08",[74,75,78,81,84],{"color":24,"id":25,"name":26,"slug":26},{"color":24,"id":76,"name":77,"slug":77},"019ddc9c-9315-735e-a7e8-8424790e8859","nuxt-ui",{"color":24,"id":79,"name":80,"slug":80},"019ddc9c-931c-743d-a1cb-e4449630fe47","collaboration",{"color":24,"id":82,"name":83,"slug":83},"019ddc9c-9327-744f-a2da-31b64c7b6ba4","editor",{"color":24,"id":31,"name":32,"slug":32},{"content":86,"createdAt":87,"id":88,"image":89,"isAffiliate":10,"isPublished":11,"publishedAt":90,"slug":91,"sourceId":92,"sourceName":93,"sourceType":16,"summary":94,"title":95,"updatedAt":96,"url":97,"urlHash":98,"tags":99},"When building modern web applications, security is not optional. One of the most important protections you can add is a Content Security Policy (CSP). But here’s the catch: 👉 CSP often blocks inline scripts and styles — which can break your app. So how do you keep your app secure without disabling useful features? That’s where CSP nonce comes in - it allows you to safely execute inline code without opening security holes. In this article, we’ll explore: What CSP nonce is What problem it solves How to implement it How it works automatically in Nuxt with nuxt-security Best practices and common pitfalls Let’s dive in. 🤔 What Is CSP Nonce? A nonce (short for number used once) is a unique, random value generated for each request. It is used in CSP to explicitly allow trusted inline scripts or styles. Example: &lt;script nonce=\"abc123\"&gt; console.log('Secure inline script') &lt;\u002Fscript&gt; And in your HTTP headers: Content-Security-Policy: script-src 'nonce-abc123' The browser will only execute scripts that have a matching nonce. CSP nonce is a whitelist mechanism: Only scripts with the correct nonce are allowed Everything else is blocked The nonce changes on every request This makes it extremely effective against XSS (Cross-Site Scripting) attacks. CSP nonce is commonly used for: SSR frameworks - Injecting initial state, Hydration scripts Analytics \u002F tracking snippets - Inline scripts required by providers Critical inline scripts - Small scripts needed before app bootstraps 🟢 What Problem Does CSP Nonce Solve? Without nonce, you usually face a trade-off: ❌ Allow inline scripts (unsafe) Content-Security-Policy: script-src 'unsafe-inline' Problem: Opens the door to XSS attacks Any injected script can run ❌ Block inline scripts completely Content-Security-Policy: script-src 'self' Problem: breaks inline event handlers breaks injected scripts (SSR hydration, state) breaks some frameworks 🟢 How to Implement CSP Nonce The process is relatively simple but let's break it down and explain each step individually. Step 1: Generate a nonce per request Example (Node.js): import crypto from 'crypto' function generateNonce() { return crypto.randomBytes(16).toString('base64') } Step 2: Add it to response headers const nonce = generateNonce() res.setHeader( 'Content-Security-Policy', `script-src 'nonce-${nonce}'` ) Step 3: Inject it into your HTML &lt;script nonce=\"{{nonce}}\"&gt; window.__INITIAL_STATE__ = {} &lt;\u002Fscript&gt; ⚠️ Critical rule The nonce in the header and HTML must match exactly. Otherwise, script will be blocked and app may break silently. 🟢 CSP Nonce in Nuxt (with nuxt-security) If you’re using Nuxt, things get much easier thanks to nuxt-security module that can: Automatically generate nonce per request Inject it into CSP headers Attach it to scripts\u002Fstyles It makes it much easier to work with CSP nonces in Nuxt. Let's take a look at the following configuration: export default defineNuxtConfig({ modules: ['nuxt-security'], security: { headers: { contentSecurityPolicy: { 'script-src': [ \"'self'\", \"'nonce-{{nonce}}'\" ] } } } }) What happens automatically: Nuxt generates a nonce per request Replaces {{nonce}} in headers Applies nonce to inline scripts You don’t need to manually wire everything You can read more here: https:\u002F\u002Fnuxt-security.vercel.app\u002F 🟢 Common Mistakes Let's take a look at the list of common mistakes to understand what to look for: ❌ Reusing the same nonce - Nonce must be unique per request and cryptographically random ❌ Forgetting to apply nonce in HTML - if you only set CSP header the scripts will still be blocked ❌ Mixing nonce with unsafe-inline - script-src 'unsafe-inline' 'nonce-abc' ❌ Caching issues - if HTML is cached nonce may not match header 🧪 Best Practices Generate nonce per request Use secure randomness (crypto) Never reuse nonce Avoid unsafe-inline Use frameworks\u002Ftools (like nuxt-security) Test CSP in report-only mode first Monitor browser console for CSP violations 📖 Learn more If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below: It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉 🧪 Advance skills A certification boosts your skills, builds credibility, and opens doors to new opportunities. Whether you're advancing your career or switching paths, it's a smart step toward success. Check out Certificates.dev by clicking this link or by clicking the image below: Invest in yourself—get certified in Vue.js, JavaScript, Nuxt, Angular, React, and more! ✅ Summary CSP nonce is a powerful mechanism that allows you to safely use inline scripts while maintaining strong security. In this article, you learned: What CSP nonce is and how it works What problem it solves (security vs flexibility) How to implement it step by step How Nuxt + nuxt-security handle it automatically Common mistakes and best practices Take care! And happy coding as always 🖥️","2026-04-27T08:00:05.007Z","019dcdf3-df7e-70dd-ba04-801be7e72c7d","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5bl1ap8ran4lh39r6v8.png","2026-04-27T06:46:57.000Z","how-to-safely-allow-inline-scripts-without-breaking-security-with-csp-nonce","019d6bd6-7fe0-7244-80dc-9a4e8751886a","Jakub Andrzejewski","This article discusses how to implement Content Security Policy (CSP) nonce to safely allow inline scripts in web applications without compromising security. It explains the challenges posed by CSP, particularly in SSR frameworks like Nuxt, and provides a step-by-step guide on generating and using nonces effectively, including how the nuxt-security module simplifies this process.","How to Safely Allow Inline Scripts Without Breaking Security with CSP Nonce","2026-06-10T14:35:13.934Z","https:\u002F\u002Fdev.to\u002Fjacobandrewsky\u002Fhow-to-safely-allow-inline-scripts-without-breaking-security-with-csp-nonce-3a2j","c7251f3ab53426399ca4e47e0e37b3c6cc6a18cf582be5d9d1521032441cb964",[100,101,104],{"color":24,"id":25,"name":26,"slug":26},{"color":24,"id":102,"name":103,"slug":103},"019dcdf3-fc84-73aa-a0be-6b7196c5a2e9","security",{"color":24,"id":105,"name":106,"slug":106},"019d6bd8-ca89-735e-a52a-ee53a80a77a9","ssr",{"content":108,"createdAt":109,"id":110,"image":111,"isAffiliate":10,"isPublished":11,"publishedAt":112,"slug":113,"sourceId":114,"sourceName":115,"sourceType":16,"summary":116,"title":117,"updatedAt":118,"url":119,"urlHash":120,"tags":121},"An agent writes most of my frontend code now. I review what it produces and tighten the architecture where it overreaches. That changes what a quality pipeline is for. You used to write tests and types so the next person on the file stayed sane. Now you write them so the agent can check its own work. Give it more ways to verify a change (types, lint, unit, component, real browser, a11y, bundle budget) and it finishes more of the ticket on its own. A red check tells it what to try next. Frontend has also grown more complicated since 2024. SSR, streaming, partial prerendering, server components, edge runtimes. Each adds a place where a change can break silently. “TypeScript plus a couple of unit tests” no longer covers it. A quality pipeline is the set of checks that run on every change (locally, on commit, in CI) to give layered confidence the change is correct, accessible, performant, and safe to ship. A testing strategy is the part of that pipeline that asserts behaviour: what the app should do, at which level, at what cost. Plan them as one system. The pipeline decides when checks run; the strategy decides which checks are worth running. Design them together so that: Each check has a clear job and runs at the cheapest stage where it can catch the problem. The feedback loop is short enough that no developer or agent skips ahead. The same checks run on a contributor’s laptop, in an agent’s sandbox, and on the CI runner. The same pipeline shape applies whether you build with Next, Nuxt, Astro, SvelteKit, Remix, or a plain Vite app. The framework choice changes which adapter you import, nothing else. Background Frontend tooling consolidated between 2023 and 2026. Vite became the default dev\u002Fbuild engine across the major frameworks. Vitest replaced Jest. Playwright became the default for E2E. ESLint adopted flat config; Biome and Oxlint emerged as much faster alternatives in Rust. TypeScript strict mode became table stakes. Renovate replaced Dependabot. In March 2026, VoidZero shipped Vite+ as the open-source culmination of that trend: one CLI that wraps Vite, Rolldown, Vitest, Oxlint, Oxfmt, and Tsdown. A modern quality pipeline’s pieces look the same regardless of framework, so I’ll describe the concept first and my stack second. My default stack For new frontend projects in 2026 I reach for Vite+ instead of wiring the toolchain by hand. Vite+ (viteplus.dev) is the unified toolchain from VoidZero, Evan You’s company. It bundles Vite, Rolldown, Vitest, Oxlint, Oxfmt, and Tsdown behind a single CLI (vp dev, vp check, vp test, vp build) and one config file. The alpha shipped open source under MIT. If you adopt the pieces one at a time, the swaps I would make are: Old default What I use Why ESLint Oxlint ~50× faster, fast enough to run on every keystroke Prettier Oxfmt ~30× faster, Prettier-compatible defaults Jest Vitest ESM-native, browser mode, same matchers webpack Vite + Rolldown ~40× faster production builds four separate configs vp check \u002F vp test \u002F vp build one CLI, one config Each piece holds up on its own. I have shipped Vitest and Oxlint in production for some time; swapping Prettier for Oxfmt and webpack for Rolldown took a day in the projects I tried. Vite+ removes the integration cost that kept teams on the older stack. The layers Think of the pipeline as concentric layers, each cheaper and faster than the one outside it. Run cheap checks first. Save the expensive ones for the things only they can catch. 1. Type safety Type safety is your first line of defence. Run your framework’s type checker in CI on every PR. Treat any new type error as a build failure. If you use TypeScript, that means tsc --noEmit (or your framework’s wrapper around it; most frameworks ship one to handle their template syntax and project references). If you don’t use TypeScript yet, adopting it is the highest-leverage change you can make. Validate untyped boundaries with a schema library (Zod, Valibot, ArkType). Parse anywhere data crosses a boundary: route params, API responses, env vars, form input. TypeScript trusts the types you write; schemas check that the data matches them at runtime. With runtime parsing in place you stop reaching for as. See why as is a shortcut to avoid. 2. Lint and format Catches style and a wide class of bugs (unused vars, unsafe any, missing deps in effects) without running the code. The conventional choice is ESLint flat config plus typescript-eslint and your framework’s plugin. The 2026 alternative is Oxlint (Rust-based, ~50× faster) paired with Oxfmt for formatting, or Biome for a single-binary lint+format combo. The trade-off: Oxlint and Biome have smaller rule sets than ESLint’s mature ecosystem, but they cover most of the high-value cases and are fast enough to run on every keystroke. For a working setup that uses Oxlint as a fast first pass and keeps ESLint for the rules Oxlint doesn’t yet cover, see my opinionated ESLint setup for Vue projects. Add these two rule families regardless of which linter you pick. They catch bugs the type system misses: eslint-plugin-regexp – ~60 correctness rules for regular expressions. Cheap to add, catches real bugs. @e18e\u002Feslint-plugin – small performance lints (e.g., prefer Set.has over Array.includes) that compound across a codebase. 3. Unit tests For pure functions, hooks, stores, and utilities. Cheap, fast, and where most logic should live. Tool: Vitest. Run on every save in watch mode; run all of them in CI. Aim for high coverage of pure modules; don’t chase coverage on UI glue. For Vue, see my guide to testing Vue composables with Vitest. 4. Component tests For components in isolation, with a real DOM and real user interactions. The biggest win in 2026 is Vitest browser mode: your component tests run in a real Chromium via Playwright instead of jsdom. Hover states, focus, layout, intersection observers, and scroll behaviour all work as they do in production. Pair this with @testing-library\u002F* for whichever framework you use; accessibility assertions on each mounted component live in layer 8 below. For a deeper walkthrough of how this fits into a full testing pyramid, see my Vue 3 testing pyramid guide. 5. API mocking Hard-coded fixtures go stale. Tests that hit a real backend are flaky. Mock at the network layer once and reuse the same handlers everywhere. Tool: MSW (Mock Service Worker). It intercepts fetch, XHR, and GraphQL with a service worker in the browser and a request interceptor in Node, so the same handler definitions work in Vitest, Vitest browser mode, Playwright, and the dev server. Define handlers once in src\u002Fmocks\u002Fhandlers.ts; load them in your test setup and (optionally) in the dev server for offline-first development. Combined with Zod (or Valibot\u002FArkType) schemas at the same boundary, you get mocks that are typed, schema-validated, and shared across every layer that hits the network. One source of truth instead of three drifting fixture folders. 6. Contract testing The mocks in layer 5 are only as good as the assumptions you bake into them. If the backend renames a field or changes a status code without telling you, every green unit and component test still passes while production breaks. Contract testing closes that gap by tying the mock to a verifiable artefact that the provider checks against. There are three styles, and they fit different team setups. Consumer-driven contracts (Pact). The frontend writes a test that records the requests it makes and the responses it expects. Pact generates a JSON contract and publishes it to a broker (the open-source Pact Broker, or hosted PactFlow). The provider runs its real test suite against that contract; if it satisfies every recorded interaction, both sides can deploy. Pact has libraries for JS\u002FTS, JVM, .NET, Go, Rust, Python, Ruby, PHP, and Swift, so the same broker spans a polyglot estate. Best when you control both ends of the wire and want the consumer to drive the schema. Provider-driven \u002F OpenAPI-based. The provider publishes an OpenAPI spec and the contract is the spec. Consumers validate their requests and assertions against it with Schemathesis (property-based fuzzing of every operation), Dredd (replays example requests from the spec against the running provider), or Spectral (lints the spec itself). Best when the provider already maintains an OAS and you don’t want to add Pact on their side. Bi-directional contracts (PactFlow). The consumer publishes a Pact contract; the provider publishes its OpenAPI spec; PactFlow proves the two are compatible without the provider having to run consumer-supplied tests. Best when consumers want consumer-driven semantics but the provider team won’t (or can’t) run Pact verification themselves. What you get for the work: Independent deploys. A contract gate replaces “is the matching E2E green?” with “does the provider satisfy every consumer’s contract?”. Consumer and provider can ship on different cadences without coordinating a release train. Faster than E2E. Verifying a contract is a unit test for the boundary; E2E spins up the real services. You catch the same class of bug an order of magnitude sooner. Catches drift the linter can’t. A field renamed on the backend fails the contract before MSW handlers or Playwright flows would notice. Skip this layer if you own both services and ship them as one unit. E2E covers the same boundary in that case, and the contract overhead doesn’t pay off. Add it the moment consumer and provider deploy on different cadences, you don’t own the provider, or a single backend serves multiple frontends that all need to keep working. For a deep dive, Contract Testing in Action (Marie Cruz &amp; Lewis Prescott, Manning) walks through Pact, bi-directional contracts, and how to introduce the practice without stalling delivery. 7. End-to-end tests For critical user journeys across real pages: signup, checkout, the one or two flows that must never break. Keep the suite small. E2E is expensive. Tool: Playwright. Run against a built preview, not the dev server. Two assertions worth wiring into a custom fixture, regardless of framework, because they catch silent regressions: Hydration mismatches. Listen for hydration warnings on console and fail the test if any appear. SSR\u002FCSR drift is one of the most common silent regressions in modern frameworks. I wrote a dedicated post on catching hydration errors in Playwright tests with a reusable fixture. CSP violations. Listen for securitypolicyviolation events. If your CSP is real, this turns every E2E run into a CSP regression test. 8. Accessibility Accessibility cuts across lint, component, E2E, and preview. Treat it as a single discipline and check the same WCAG rule set at every cheap-enough stage. Lint. eslint-plugin-jsx-a11y (React\u002FJSX), eslint-plugin-vuejs-accessibility (Vue), eslint-plugin-astro — catch missing alt, role mismatches, and other static violations before tests run. Component. axe-core via jest-axe for jsdom, or @axe-core\u002Fplaywright in Vitest browser mode. Assert no violations on every mounted component, and add a meta-test that fails if any component test lacks an a11y assertion so the practice doesn’t slide. E2E. @axe-core\u002Fplaywright on each critical journey — same engine as the component layer, but on the real composed page where many violations only appear once everything is wired together. Preview. Lighthouse’s accessibility category (run as part of layer 10) or Pa11y CI on a list of routes for a dedicated, auditable report. Manual. Storybook’s a11y addon, keyboard-only walkthroughs of new flows, and screen-reader spot-checks. Automated tools catch roughly 30% of WCAG issues; the rest needs a human. This is no longer optional in the EU: the European Accessibility Act took effect in mid-2025, so most B2C and many B2B products operating in EU markets are now legally required to meet WCAG 2.1 AA equivalence. For a framework-specific checklist, see my Vue accessibility blueprint. 9. Visual regression Catches unintended UI drift that unit and E2E tests miss. Chromatic (hosted, Storybook-native) or Playwright screenshots + a diff tool like Lost Pixel for self-hosted. For a Vitest-native approach, see how to do visual regression testing in Vue with Vitest. onlyChanged: true keeps it cheap: only re-snapshot stories whose dependencies changed. Gate on PR; review diffs as part of code review. 10. Performance and bundle size Performance regressions are silent unless you measure them. Lighthouse CI on a preview deployment. Run it against both a light and dark color scheme; contrast regressions show up only in one. size-limit or your framework’s bundle analyzer on PR for bundle deltas. Set explicit budgets and fail the build when they’re exceeded. Lab measurements catch regressions before merge. To see what real users experience, and to find bottlenecks while you’re writing the code, see layer 15 below. 11. Dead code and dependency hygiene Unused code is a tax on every other check. Knip to find unused files, exports, and dependencies. Configure per-workspace if you have a monorepo. Renovate for automated dependency updates with grouping and a sane schedule. OSV-Scanner for vulnerabilities and Gitleaks for secrets, gated to high-severity only to avoid alert fatigue. Generate an SBOM (Software Bill of Materials) on every build with Syft, Trivy, or cdxgen, in CycloneDX or SPDX format. This is shifting from “nice to have” to “regulated requirement” in 2026 (EU CRA, US executive orders), and it’s the same artefact your security team uses to answer customer vulnerability questionnaires. 12. Internationalisation drift If you ship in more than one language, untranslated strings slip through. A mature i18n library plus a drift checker in CI catches them. i18n libraries – i18next, vue-i18n, FormatJS \u002F react-intl, and Lingui all expose a missing-key handler you can fail the build on, plus extractor CLIs that refuse to ship if a string has no translation entry. Lint your source for hardcoded strings. ESLint has eslint-plugin-i18next and @intlify\u002Feslint-plugin-vue-i18n to flag bare strings in JSX\u002Ftemplates and unused or missing keys. Oxlint doesn’t yet ship i18n-specific rules, so run it as the fast first pass and keep these ESLint plugins for the i18n layer. Lunaria compares each locale against a source locale and reports missing or stale keys. It works with any project that has translation files; you can publish a public status dashboard from the same data. 13. Preview deployments The cheapest way to enable manual review and to give E2E, Lighthouse, and visual-regression checks something realistic to run against. Vercel, Netlify, or Cloudflare Pages will give you a unique URL per PR for free. Wire your downstream checks to that URL. 14. Automated code review In 2026, AI code review is a standard pipeline stage. It runs before any human reviewer touches the PR and catches issues the layers above miss: logic mistakes, missing edge cases, security smells, and the small inconsistencies that lint rules can’t express. CodeRabbit, Greptile, and Vercel Agent are the main options. Recent benchmarks put Greptile’s bug-catch rate around 82% versus CodeRabbit’s ~44%, but Greptile produces more false positives and runs slower; CodeRabbit covers more git platforms. Have it run alongside specialist scanners (secrets, vulnerabilities, workflow lint, shell\u002Fyaml lint) so a single bot comment summarises every machine-checkable concern on the PR. Pause the bot on Renovate \u002F Dependabot PRs to avoid noise on mechanical updates. Treat the AI reviewer as a high-recall first pass that reduces human review without replacing it. If you want to add an AI agent that goes one step further and exercises the app in a real browser, see how I run automated QA with Claude Code, Agent Browser, and GitHub Actions. 15. Runtime observability Layers 1–13 give you confidence at merge time. Once a change is in production, and while you’re writing it, you also want a live view of what the app is doing. The same instrumentation answers both questions. Use OpenTelemetry as the SDK. It’s the only vendor-neutral option, and the JS ecosystem caught up in 2025–2026: stable web SDK, official auto-instrumentations for document-load, fetch, xhr, and user-interaction, and OTLP support in every backend that matters. Browser SDK. @opentelemetry\u002Fsdk-trace-web plus the auto-instrumentations emits OTLP\u002FHTTP. Wrap web-vitals into OTel metrics so LCP\u002FINP\u002FCLS land on the same backend as the trace that produced them. One pipe instead of two. SSR \u002F edge. Next, Nuxt, SvelteKit, and Astro all expose OTel hooks. Set a single service.name resource attribute and one request stitches together: edge → SSR → hydration → client interaction, all in one trace. One collector, two backends. Run an OpenTelemetry Collector with two exporters. In dev, point it at Jaeger or Grafana Tempo running via docker-compose; open localhost:16686 and you can watch every fetch, render, and hydration span as you click through the app. In prod, swap the exporter to Honeycomb, Grafana Cloud, Dash0, or Sentry’s OTel ingest. Same SDK, same instrumentations, different OTLP endpoint. Sample, or pay. ParentBased(TraceIdRatioBased(0.05)) in prod, AlwaysOn in dev. Tail-sample at the collector to keep the slow and error traces and drop the rest, so the signal that matters survives without renting cloud storage for every render. The dev-time payoff is the part most teams underuse. The next time someone asks why a page is slow on a real device, you already have the trace from when they loaded it. Where each layer runs Same layers, different stages. Pick the cheapest stage where each check can catch the problem. Stage What runs Editor Type checker LSP, linter, Vitest watch Pre-commit Format and lint on staged files only CI on PR Typecheck, full lint, unit, component, contract verify, build, knip, size-limit, AI review CI on preview URL E2E, accessibility (axe + Lighthouse), visual regression Post-merge \u002F nightly Full E2E matrix, dependency updates, security scans, SBOM publish Dev server \u002F production OpenTelemetry traces and metrics: live in dev, sampled in prod For wiring local hooks themselves, Lefthook has become the default modern alternative to Husky: a single Go binary, declarative YAML config, and parallel execution of lint\u002Fformat\u002Ftest commands on staged files. Commit a lefthook.yml to the repo, run lefthook install once, and contributors get the same hook setup automatically. Pair it with lint-staged (or Lefthook’s built-in {staged_files} substitution) so pre-commit only runs against the files that changed. That fast-check pattern keeps the hook under a couple of seconds. Git 2.54 added config-based hooks, which means a small project no longer needs an external hook manager at all. You define hooks in .gitconfig instead of as scripts under .git\u002Fhooks: [hook \"linter\"] event = pre-commit command = pnpm exec oxlint --staged [hook \"format\"] event = pre-commit command = pnpm exec oxfmt --check Multiple hooks per event run in order. Disable a single hook with hook.&lt;name&gt;.enabled = false (useful for opting one repo out of a system-wide config), and list the active ones with git hook list pre-commit. The traditional .git\u002Fhooks\u002F* scripts still run last, so existing setups keep working. For a small project this covers most of what Lefthook does without an extra binary. Lefthook still has the edge for parallel execution and staged-file substitution. One valid alternative skips commit hooks and runs everything server-side in CI as required status checks. You trade a slower red-CI feedback loop for never blocking a contributor with a flaky local hook. If GitHub Actions is the CI you’re wiring this onto, GitHub Actions in Action (Kaufmann, Bos &amp; de Vries, Manning) covers workflow design, reusable actions, matrix builds, secrets, and self-hosted runners. It pays off once you outgrow the default templates and start wiring the layers above into shared workflows. What this gets you Regressions caught before merge. A typed schema at the boundary plus a Playwright check on the critical path catches more shipped bugs than any single layer alone. Refactors get safer. Strict types and a healthy unit and component test suite let you change internals without breaking surface behaviour. Onboarding gets shorter. A new contributor can run one command, see green, and trust that CI will tell them if they break something. No one becomes the bottleneck. The pipeline enforces the standard for accessibility, performance, and i18n, so quality stops riding on a single contributor. Picking your testing shape The layers above tell you what to run. They don’t tell you how much weight to give each one. A solo dev shipping a Vite app needs a different mix than a fifty-engineer team coordinating across a dozen services. The industry argues about this in shapes. The classical Pyramid (Mike Cohn) puts most of the weight on unit tests and very little on E2E. Kent C. Dodds’ Trophy moves the weight to integration tests because frontend bugs live at the component-interaction layer. Spotify’s Honeycomb pushes weight onto integrated and contract tests because in a microservices world, isolation tests prove little and full E2E is brittle. The Ice-cream cone is the anti-pattern you end up with by accident: lots of slow E2E on top, almost nothing underneath. The right answer is “it depends,” but you can be precise about what it depends on. As web.dev puts it in Pyramid or Crab, “the testing strategy that’s right for your team is unique to your project’s context”. Pactflow, from the contract-testing camp, goes further: at scale, full E2E is a tax with diminishing returns once teams and services multiply. The four inputs that change the answer most: Team size. Six developers can keep a real E2E suite green together; sixty cannot. Coordination cost is what kills E2E suites at scale. Backend control. If you don’t own the backend, contract testing goes from “nice” to “the only way you can change anything safely”. Number of services in the flow. Each new service multiplies the surface that has to be set up, seeded, and reset for an E2E run. Deployment cadence. A daily-merge team can’t afford a flaky twenty-minute suite; a quarterly-release team can. Pick yours and the shape that fits will appear: The same pyramid that helps one team can block another from shipping. Pick the shape that fits your constraints. Picking your battles You don’t need every layer on day one. A reasonable order to add them: TypeScript strict + a fast linter + a formatter, wired into Lefthook so format and lint run on staged files at commit time. Vitest for utilities, run on PR. MSW handlers for any module that hits the network, shared between tests and the dev server. Playwright for the single most important user journey, with hydration and CSP listeners wired into a shared fixture. Contract testing the moment consumer and provider deploy on different cadences. Pact if you control both sides, OpenAPI validation if the provider already publishes a spec. Preview deployments and Lighthouse CI (light and dark). OpenTelemetry traces and metrics. Point the SDK at a local Jaeger via docker-compose the moment a “why is this slow?” question takes more than five minutes to answer. Same SDK in prod (different OTLP endpoint) once you have real users. Storybook + visual regression once the design system stabilises. Accessibility audits in component tests and E2E. Knip and bundle-size budgets once the codebase has weight. i18n drift checking once you ship a second locale. AI code review and SBOM generation once the project has external stakeholders to answer to: reviewers, customers, or compliance. Each layer should pay for itself in caught regressions or saved review time. Remove the ones that don’t. Supply chain defaults The pipeline above catches the bugs you write. None of it stops a compromised dependency from running code on your laptop. The 2025–2026 wave of npm attacks (Shai-Hulud, the Rspack postinstall cryptominer, the axios 1.14.1 hijack) made the package manager’s defaults a real part of your security posture. I use pnpm on every new project. Three of its settings do most of the work. 1. Lifecycle scripts blocked by default. Since pnpm 10, preinstall and postinstall scripts in dependencies do not run on pnpm install. You opt specific packages in via pnpm.onlyBuiltDependencies in package.json. Most historical supply chain payloads shipped through postinstall, so this default removes one of the biggest attack vectors. 2. minimumReleaseAge. A pnpm 10.16+ setting that refuses to resolve a published version until it is at least N minutes old. Set it to 1440 (one day) or 10080 (one week) in pnpm-workspace.yaml. Most compromised packages get detected and unpublished within hours, so a 24-hour delay covers the common published-and-pulled incidents. pnpm 11 makes one day the default. Use minimumReleaseAgeExclude for the few internal or first-party packages you need to install the moment they ship. 3. blockExoticSubdeps. Refuses transitive dependencies pinned to git repositories or tarball URLs. Closes a common path for typo-squatting and dependency confusion. A minimal pnpm-workspace.yaml for a new project: minimumReleaseAge: 1440 blockExoticSubdeps: true onlyBuiltDependencies: - esbuild - sharp Pair this with OSV-Scanner and Gitleaks in CI (layer 11 of the pipeline) and you cover both the install-time and the audit-time sides of supply chain security. Related resources If you want to read more or start implementing this: Tools Vite+ – unified toolchain from VoidZero (Vite, Rolldown, Vitest, Oxlint, Oxfmt, Tsdown) pnpm supply chain security – the full list of defaults and settings discussed above Vitest – including browser mode Playwright MSW – network-level API mocking for browser and Node Pact and PactFlow – consumer-driven and bi-directional contract testing Schemathesis, Dredd, Spectral – OpenAPI-based contract testing and linting Lefthook – fast Git hooks manager (modern Husky alternative) Storybook Knip Oxlint Biome Lighthouse CI Lunaria – i18n drift detection size-limit axe-core Syft – SBOM generation CodeRabbit, Greptile – AI code review Reference The Practical Test Pyramid – Ham Vocke’s canonical write-up of Mike Cohn’s pyramid. The Testing Trophy – Kent C. Dodds’ model for where to put the weight of your tests. Testing of Microservices (Honeycomb) – Spotify’s case for integrated tests over isolated unit tests in a microservices world. Pyramid or Crab? Find a testing strategy that fits – web.dev on choosing a shape for your context. Proving E2E tests are a scam – Pactflow’s contract-first counter-position. Contract Testing in Action – Marie Cruz &amp; Lewis Prescott (Manning) on consumer-driven and bi-directional contracts in practice. GitHub Actions in Action – Kaufmann, Bos &amp; de Vries (Manning) on workflows, reusable actions, matrix builds, and self-hosted runners. Frontend testing guide: 10 essential rules for naming tests","2026-04-25T18:07:54.197Z","019dc5d3-a141-771d-be4c-4fce2ee29fe6","https:\u002F\u002Falexop.dev\u002Fposts\u002Fa-modern-quality-pipeline-and-testing-strategy-for-frontend-projects\u002Findex.png","2026-04-25T00:00:00.000Z","a-modern-quality-pipeline-and-testing-strategy-for-frontend-projects","019d70dd-e3e7-76db-84a4-87b896dea004","alexop.dev","The article discusses the evolution of frontend quality pipelines and testing strategies, emphasizing the need for comprehensive checks to ensure code quality in increasingly complex environments. It highlights the importance of integrating various tools like Vite, Vitest, and Oxlint into a unified pipeline that can adapt to different frameworks, including Nuxt. The author advocates for a modern approach where the pipeline and testing strategy are designed together to enhance developer efficiency and code reliability.","A Modern Quality Pipeline and Testing Strategy for Frontend Projects","2026-04-25T18:08:02.112Z","https:\u002F\u002Falexop.dev\u002Fposts\u002Fmodern-frontend-quality-pipeline\u002F","b4cf663ff2cb6932a7923d040313f475d0f879aa91eb3f8a52678d36b150b7c9",[122,125,128,131,132],{"color":24,"id":123,"name":124,"slug":124},"019d9cf0-c9f8-7411-beed-02265d8271db","vite",{"color":24,"id":126,"name":127,"slug":127},"019dc5d3-c054-70e2-bca4-08e2a2b9a096","vitest",{"color":24,"id":129,"name":130,"slug":130},"019d70de-3c07-76bf-9688-9619e1b0d427","testing",{"color":24,"id":105,"name":106,"slug":106},{"color":24,"id":25,"name":26,"slug":26},{"content":134,"createdAt":135,"id":136,"image":137,"isAffiliate":10,"isPublished":11,"publishedAt":138,"slug":139,"sourceId":40,"sourceName":41,"sourceType":42,"summary":140,"title":141,"updatedAt":142,"url":143,"urlHash":144,"tags":145},"Large Vue\u002FNuxt applications can quickly become a labyrinth of components, modules, and plugins. In this talk, we'll explore how ...","2026-04-24T12:00:04.207Z","019dbf5c-825c-704a-bb17-a01866ec5e99","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FGvFuUaoNt_4\u002Fhqdefault.jpg","2026-04-24T10:00:06.000Z","jakub-andrzejewski-dungeon-mastering-your-vue-app-from-chaos-to-order","This article discusses strategies for managing large Vue and Nuxt applications, focusing on organizing components, modules, and plugins to maintain order and efficiency. The talk aims to provide insights on navigating the complexities of developing with Vue and Nuxt.","Jakub Andrzejewski   Dungeon Mastering Your Vue App From Chaos to Order","2026-04-24T12:00:13.418Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=GvFuUaoNt_4","781306580c8d200f4cd2400045700da8ead3092ef9fcaa1f67dc2450d7019f15",[146,147,148],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":25,"name":26,"slug":26},{"color":24,"id":56,"name":57,"slug":57},{"content":150,"createdAt":151,"id":152,"image":153,"isAffiliate":10,"isPublished":11,"publishedAt":154,"slug":155,"sourceId":40,"sourceName":41,"sourceType":42,"summary":156,"title":157,"updatedAt":158,"url":159,"urlHash":160,"tags":161},"Ever wondered how deep your knowledge of the framework really goes? Beyond the daily routine of components and ...","2026-04-22T12:00:01.693Z","019db50f-c08a-70b8-8987-727e9b105054","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FfNJCzx4k2Ik\u002Fhqdefault.jpg","2026-04-22T10:01:13.000Z","thorsten-seyschab---vue-think-you-know-it-all-the-ultimate-vue-live-quiz","This article introduces an engaging live quiz designed to test your knowledge of Vue.js, challenging participants to go beyond their everyday understanding of the framework. It promises to reveal the depths of your expertise in Vue through a fun and interactive format.","Thorsten Seyschab - Vue Think You Know It All The Ultimate Vue Live Quiz","2026-04-22T12:00:13.537Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=fNJCzx4k2Ik","3acaa865cfa59009f9baeaf70e602f21c68f900e6f62702e325ccab335ecaa1f",[162,163],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":164,"name":165,"slug":165},"019d6bd8-fba5-743f-8f9f-4e23c0b31581","tutorial",{"content":167,"createdAt":168,"id":169,"image":170,"isAffiliate":11,"isPublished":11,"publishedAt":171,"slug":172,"sourceId":173,"sourceName":174,"sourceType":175,"summary":176,"title":177,"updatedAt":178,"url":179,"urlHash":180,"tags":181},"Learn how to use composables in Nuxt effectively, avoid common SSR and state pitfalls, and build production-ready patterns in this practical 15-minute guide.","2026-04-25T18:07:52.417Z","019dc5d3-9a44-72c8-8f6c-0262bb4041d2","https:\u002F\u002Fapi.certificates.dev\u002Fstorage\u002FfLgc0NdkTT9Vgq2AhUNulkZwaj6txgVnAAOsHKFu.png","2026-04-22T07:00:00.000Z","composable-best-practices-in-nuxt","019d9eed-d835-729a-a029-7e6320cb67ed","Certificates.dev","certificatesdev","This article provides a practical guide on using composables in Nuxt, highlighting best practices to avoid common pitfalls related to server-side rendering (SSR) and state management. It aims to help developers build production-ready patterns efficiently.","Composable Best Practices in Nuxt","2026-04-25T18:08:01.170Z","https:\u002F\u002Fcertificates.dev\u002Fblog\u002Fcomposable-best-practices-in-nuxt?friend=MOKKAPPS","b506b0480c7b2d23a037e48443ae0e8d769398478e0b1f5d56539112166fea86",[182,183,186,189,190],{"color":24,"id":25,"name":26,"slug":26},{"color":24,"id":184,"name":185,"slug":185},"019d6bd8-ca21-71c5-a236-37d94fe57d24","composition-api",{"color":24,"id":187,"name":188,"slug":188},"019da076-78fb-706b-9a4c-cee0c989cfff","state-management",{"color":24,"id":105,"name":106,"slug":106},{"color":24,"id":164,"name":165,"slug":165},{"content":192,"createdAt":193,"id":194,"image":195,"isAffiliate":10,"isPublished":11,"publishedAt":196,"slug":197,"sourceId":40,"sourceName":41,"sourceType":42,"summary":198,"title":199,"updatedAt":200,"url":201,"urlHash":202,"tags":203},"Back in 2013, before Vue, React, and modern tooling, I built the global campaign site for Need for Speed Rivals as the only ...","2026-04-21T12:00:01.702Z","019dafe9-6493-754d-88cf-f6f14fbef71d","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FUOvBq94PM1A\u002Fhqdefault.jpg","2026-04-21T10:00:06.000Z","tim-benniks-from-vanilla-chaos-to-vue-zen-rebuilding-eas-2013-need-for-speed-rivals-web-campaign","Tim Benniks shares his journey of transforming the chaotic vanilla JavaScript web campaign for EA's 2013 Need for Speed Rivals into a streamlined Vue.js application. This transition highlights the benefits of modern frameworks in enhancing development efficiency and user experience.","Tim Benniks   From Vanilla Chaos to Vue Zen Rebuilding EA’s 2013 Need For Speed Rivals Web Campaign","2026-04-21T12:00:11.336Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=UOvBq94PM1A","32e7bf0e5914f3315b3ab987cf7269b14d692ab92ae8183b46af699a25357f4e",[204,205,208],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":206,"name":207,"slug":207},"019dafe9-8a6d-7218-81f1-37052cbe9b78","development",{"color":24,"id":56,"name":57,"slug":57},{"content":210,"createdAt":211,"id":212,"image":213,"isAffiliate":10,"isPublished":11,"publishedAt":214,"slug":215,"sourceId":40,"sourceName":41,"sourceType":42,"summary":216,"title":217,"updatedAt":218,"url":219,"urlHash":220,"tags":221},"We're going to explore how Vue can power more than user interfaces—by running its reactivity system on the backend. No installs ...","2026-04-20T12:00:01.595Z","019daac3-0825-7156-a2a8-afa51eb8b7aa","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FhFgqGgaHo0U\u002Fhqdefault.jpg","2026-04-20T10:00:06.000Z","marc-backes---the-backend-is-reactive-vue-beyond-the-browser","This article discusses the potential of using Vue's reactivity system on the backend, highlighting its capabilities beyond just user interfaces. It emphasizes the innovative ways Vue can be utilized in server-side applications without the need for installations.","Marc Backes - The Backend is Reactive Vue Beyond the Browser","2026-04-20T12:00:11.606Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=hFgqGgaHo0U","51b5110f9f258c012f7a27ea7bf0a5b0efedb525e75f06780cff72f721f42a37",[222,223,226],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":224,"name":225,"slug":225},"019daac3-2f85-7393-b891-9da3891267ca","reactivity",{"color":24,"id":227,"name":228,"slug":228},"019d9d20-e077-71cc-a605-8ac2a1566143","backend",{"content":230,"createdAt":231,"id":232,"image":233,"isAffiliate":10,"isPublished":11,"publishedAt":234,"slug":235,"sourceId":40,"sourceName":41,"sourceType":42,"summary":236,"title":237,"updatedAt":238,"url":239,"urlHash":240,"tags":241},"Vue Router's next version treats your URL as fully typed state. Learn how to extract, transform, and validate parameters from paths ...","2026-04-18T12:00:03.190Z","019da076-565b-7659-9302-3e6a65e5d244","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FWz57d7ijW28\u002Fhqdefault.jpg","2026-04-18T10:00:06.000Z","eduardo-san-martin-morote---typesafe-state-in-your-url","The next version of Vue Router introduces a typesafe approach to handling URLs, allowing developers to treat URLs as fully typed state. This article covers how to extract, transform, and validate parameters from paths effectively.","Eduardo San Martin Morote - Typesafe state in your URL","2026-04-18T12:00:11.956Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Wz57d7ijW28","ca5b7486d4adcc6bbfafa5d357346210659ec1ffe34b25cf428e96f0a2a4ce07",[242,245,248],{"color":24,"id":243,"name":244,"slug":244},"019da076-78e6-76bd-b0d4-4e0597d36378","vue-router",{"color":24,"id":246,"name":247,"slug":247},"019d6bd9-0049-72ba-8cfa-139b1c1b249d","typescript",{"color":24,"id":187,"name":188,"slug":188},{"content":250,"createdAt":251,"id":252,"image":253,"isAffiliate":10,"isPublished":11,"publishedAt":254,"slug":255,"sourceId":256,"sourceName":257,"sourceType":42,"summary":258,"title":259,"updatedAt":260,"url":261,"urlHash":262,"tags":263},"Thank you InsForge for sponsoring this video! https:\u002F\u002Finsforge.dev\u002F Nuxt Course: https:\u002F\u002Fwww.learnnuxt.dev In this video, I'll ...","2026-04-17T20:27:43.729Z","019d9d20-c4e3-74d8-abc9-b905cb285ced","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FIR4l6GjoxH8\u002Fhqdefault.jpg","2026-04-17T17:00:17.000Z","build-a-backend-in-minutes-with-ai-prompts-claude-insforge-amp-nuxt","019d6c23-e4e7-7379-a6f7-80d0e5734cd1","John Komarnicki","This article discusses how to quickly build a backend using AI prompts with Claude and InsForge, while integrating Nuxt into the process. It highlights the tools and resources available for developers looking to streamline backend development.","Build a Backend in Minutes with AI Prompts | Claude + InsForge &amp; Nuxt","2026-04-17T20:27:50.745Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=IR4l6GjoxH8","4e7e7fe0f09ec09c17d4760065d998b8251ff49d6184fc19b7d17093010c3688",[264,265,266,267],{"color":24,"id":25,"name":26,"slug":26},{"color":24,"id":227,"name":228,"slug":228},{"color":24,"id":31,"name":32,"slug":32},{"color":24,"id":164,"name":165,"slug":165},{"content":269,"createdAt":270,"id":271,"image":272,"isAffiliate":10,"isPublished":11,"publishedAt":273,"slug":274,"sourceId":40,"sourceName":41,"sourceType":42,"summary":275,"title":276,"updatedAt":277,"url":278,"urlHash":279,"tags":280},"This talk explores building a production-grade Vue SPA using Vite + Rolldown for modern bundling, Nitro as a lightweight server ...","2026-04-17T19:35:11.345Z","019d9cf0-aadb-7522-87ac-9f74944f20e1","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FTlTkXI1e3xw\u002Fhqdefault.jpg","2026-04-17T10:00:06.000Z","pooya-parsa---full-stack-agnostic-apps-in-the-agentic-era-vite-nitro","The talk by Pooya Parsa delves into the development of production-grade Vue single-page applications (SPAs) utilizing Vite for modern bundling and Nitro as a lightweight server. It emphasizes the importance of building agnostic applications in the contemporary tech landscape.","Pooya Parsa - Full Stack Agnostic Apps in the Agentic Era (Vite + Nitro)","2026-04-17T19:35:19.271Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=TlTkXI1e3xw","6bfb508734c0bf8b15fc6b79afa7970a0ccc6e553459020a69bdbaad2faa91f1",[281,282,283,286,289],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":123,"name":124,"slug":124},{"color":24,"id":284,"name":285,"slug":285},"019d9cf0-c9fc-76a0-8c4e-b818a89c3774","nitro",{"color":24,"id":287,"name":288,"slug":288},"019d9cf0-ca00-749d-9101-1c63bf62e215","spas",{"color":24,"id":56,"name":57,"slug":57},{"content":291,"createdAt":270,"id":292,"image":293,"isAffiliate":10,"isPublished":11,"publishedAt":294,"slug":295,"sourceId":40,"sourceName":41,"sourceType":42,"summary":296,"title":297,"updatedAt":298,"url":299,"urlHash":300,"tags":301},"As AI agents are becoming active “users” of your software, they must learn to interact with complex UIs safely and effectively.","019d9cf0-aadb-7522-87ac-a1a8baea8eb5","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FkR2GHGPLxSM\u002Fhqdefault.jpg","2026-04-16T10:00:06.000Z","rijk-van-zanten---guiding-agentic-ai-with-vue-and-pinia","The article discusses the integration of agentic AI with user interfaces built using Vue and Pinia, emphasizing the importance of guiding AI agents to interact with complex UIs safely and effectively.","Rijk van Zanten - Guiding Agentic AI with Vue and Pinia","2026-04-17T19:35:19.243Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=kR2GHGPLxSM","b66757cbea3dae368d5c7fe2c3ee86b47b67c447471c2e6c144211d306503518",[302,303,306],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":304,"name":305,"slug":305},"019d9cf0-c9de-70b2-9057-76d771c3379e","pinia",{"color":24,"id":28,"name":29,"slug":29},{"content":308,"createdAt":309,"id":310,"image":311,"isAffiliate":10,"isPublished":11,"publishedAt":312,"slug":313,"sourceId":40,"sourceName":41,"sourceType":42,"summary":314,"title":315,"updatedAt":316,"url":317,"urlHash":318,"tags":319},"The place to be for the highly anticipated State of Nuxt for the year 2026. Previous years introduced a new architecture (Nuxt 3), ...","2026-04-17T20:27:43.053Z","019d9d20-c23d-74ce-95cd-60771aa2fe5f","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002Fwe4gdQikxm8\u002Fhqdefault.jpg","2026-04-14T10:00:06.000Z","daniel-roe---state-of-nuxt-2026","The article discusses the upcoming State of Nuxt for 2026, highlighting the advancements and changes since the introduction of Nuxt 3. It sets the stage for what to expect in the future of the Nuxt framework.","Daniel Roe - State of Nuxt 2026","2026-04-17T20:27:50.743Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=we4gdQikxm8","cc37b2ce2cfc3070e8b7c5f75bcf018590b46fdaa43fa0b42fbb4d9d22719279",[320,321,322],{"color":24,"id":25,"name":26,"slug":26},{"color":24,"id":56,"name":57,"slug":57},{"color":24,"id":323,"name":324,"slug":324},"019d9d20-dfb9-7041-9973-39c545b33a2d","release",{"content":326,"createdAt":309,"id":327,"image":328,"isAffiliate":10,"isPublished":11,"publishedAt":329,"slug":330,"sourceId":40,"sourceName":41,"sourceType":42,"summary":331,"title":332,"updatedAt":333,"url":334,"urlHash":335,"tags":336},"The number 1 annual anticipated update from the Creator of Vue himself, Evan You! You may expect updates like Evan did ...","019d9d20-c23d-74ce-95cd-6569ebec4e77","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002Fa9_Ud5MFTjU\u002Fhqdefault.jpg","2026-04-13T08:54:41.000Z","evan-you---state-of-vue-2026","Evan You shares insights and updates on the future of Vue in the annual 'State of Vue 2026' address, highlighting key developments and expectations for the framework. This event is highly anticipated by the Vue community as it outlines the direction and innovations in Vue.js.","Evan You - State of Vue 2026","2026-04-17T20:27:50.542Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=a9_Ud5MFTjU","6633003842ca2e59cf50193ac27f511c63bd2c17cb4556567d16af5cff1abec9",[337,338],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":323,"name":324,"slug":324},{"content":340,"createdAt":341,"id":342,"image":343,"isAffiliate":10,"isPublished":11,"publishedAt":344,"slug":345,"sourceId":114,"sourceName":115,"sourceType":16,"summary":346,"title":347,"updatedAt":348,"url":349,"urlHash":350,"tags":351},"Your E2E tests pass. The page loads, buttons work. But open the browser console: Hydration failed because the server rendered HTML didn't match the client. This is a hydration mismatch. The server sent one thing and the client replaced it with something else. The page still works, so you don’t notice. Your tests don’t check for it, so they pass. What are SSR and hydration? SSR (server-side rendering) means the server generates HTML and sends it to the browser before JavaScript loads. Users see content before client code boots, and search engines can index it. Astro and Nuxt build on this model. Hydration is the next step: client JavaScript takes over the server-rendered HTML, attaching event handlers and state to the existing markup. The contract: the first client render must match what the server sent. When it does not match, the framework discards the server HTML and re-renders on the client. That re-render is a hydration mismatch. Common causes Anything that produces different HTML on client and server: Reading localStorage or window.matchMedia() during render Calling new Date() or Math.random() during render Formatting dates or numbers differently across server and client Rendering conditional branches based on browser-only state Theme toggles and locale formatting cause most of them. If you use Vue with SSR, the window is not defined error comes from the same root cause. VueUse has a pattern for it: A real bug I found I was working on an Astro page and my theme hook was reading browser state during the first render: function getInitialTheme(): Theme { const stored = localStorage.getItem(SITE.themeStorageKey); if (stored === \"light\" || stored === \"dark\") return stored; return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\"; } export function useTheme() { const [theme, setTheme] = useState&lt;Theme&gt;(getInitialTheme); } The server defaulted to dark, but the browser picked light. React saw the mismatch, logged a hydration warning, and re-rendered from scratch. The page still worked, the button still existed. Normal E2E tests passed. The fix: start with a deterministic value, resolve browser state after mount. export function useTheme() { const [theme, setTheme] = useState&lt;Theme&gt;(\"dark\"); const [mounted, setMounted] = useState(false); useEffect(() =&gt; { const preferredTheme = getPreferredTheme(); document.documentElement.classList.toggle(\"dark\", preferredTheme === \"dark\"); setTheme(preferredTheme); setMounted(true); }, []); useEffect(() =&gt; { if (!mounted) return; const root = document.documentElement; root.classList.toggle(\"dark\", theme === \"dark\"); localStorage.setItem(SITE.themeStorageKey, theme); }, [mounted, theme]); return { theme, setTheme, toggleTheme: () =&gt; setTheme((t) =&gt; (t === \"dark\" ? \"light\" : \"dark\")) }; } The core idea Listen to the browser console during a Playwright test. If a hydration warning appears, fail the test. React and Vue log hydration mismatches to the console. You don’t check the console during automated tests, so this fixture does. The fixture Fixtures are Playwright's way of setting up and tearing down what each test needs. Built-in fixtures like `page` and `browser` come for free. You create custom ones with `base.extend()`. Each fixture runs when a test requests it and gets cleaned up afterward. The fixture below injects `hydrationErrors` and `runtimeErrors` into every test that asks for them. I first saw this approach in the npmx.dev open source project and adapted it for my Astro site. My version covers React and Vue hydration strings and catches uncaught runtime exceptions: const HYDRATION_ERROR_PATTERNS = [ \u002Fhydration failed because the server rendered html didn't match the client\u002Fi, \u002Fhydration completed but contains mismatches\u002Fi, \u002Fhydration text content mismatch\u002Fi, \u002Fhydration node mismatch\u002Fi, \u002Fhydration attribute mismatch\u002Fi, ]; function isHydrationError(text: string): boolean { return HYDRATION_ERROR_PATTERNS.some((pattern) =&gt; pattern.test(text)); } function toConsoleText(message: ConsoleMessage): string { return message.text().trim(); } export const test = base.extend&lt;{ hydrationErrors: string[]; runtimeErrors: string[]; }&gt;({ hydrationErrors: async ({ page }, use) =&gt; { const hydrationErrors: string[] = []; const handleConsole = (message: ConsoleMessage) =&gt; { const text = toConsoleText(message); if (isHydrationError(text)) { hydrationErrors.push(text); } }; page.on(\"console\", handleConsole); await use(hydrationErrors); page.off(\"console\", handleConsole); }, runtimeErrors: async ({ page }, use) =&gt; { const runtimeErrors: string[] = []; const handleConsole = (message: ConsoleMessage) =&gt; { const text = toConsoleText(message); if (message.type() === \"error\" &amp;&amp; text.length &gt; 0 &amp;&amp; !isHydrationError(text)) { runtimeErrors.push(text); } }; const handlePageError = (error: Error) =&gt; { runtimeErrors.push(error.message); }; page.on(\"console\", handleConsole); page.on(\"pageerror\", handlePageError); await use(runtimeErrors); page.off(\"console\", handleConsole); page.off(\"pageerror\", handlePageError); }, }); export { expect }; Drop this into test\u002Fe2e\u002Ftest-utils.ts and import from there instead of @playwright\u002Ftest. Related: a full AI-driven QA workflow with Playwright: Using it test(\"home page hydrates cleanly\", async ({ page, hydrationErrors, runtimeErrors }) =&gt; { await page.goto(\"\u002F\", { waitUntil: \"domcontentloaded\" }); await expect(page.getByRole(\"heading\", { name: \"Home\" })).toBeVisible(); expect(hydrationErrors).toEqual([]); expect(runtimeErrors).toEqual([]); }); Start with your homepage. Add one interactive route, then one with a theme toggle or client-only widget. That surfaces most bugs. How npmx.dev does it at scale The npmx.dev project tests hydration correctness for every combination of user settings across every page, around 48 checks from a single fixture. They inject localStorage values via Playwright’s page.addInitScript() before navigation, simulating a returning user with saved preferences. Returning users with non-default settings trigger most hydration mismatches. const PAGES = [\"\u002F\", \"\u002Fabout\", \"\u002Fsettings\", \"\u002Fcompare\", \"\u002Fsearch\", \"\u002Fpackage\u002Fnuxt\"]; test.describe(\"color mode: dark\", () =&gt; { for (const page of PAGES) { test(`${page}`, async ({ page: pw, goto, hydrationErrors }) =&gt; { await injectLocalStorage(pw, { \"npmx-color-mode\": \"dark\" }); await goto(page, { waitUntil: \"hydration\" }); expect(hydrationErrors).toEqual([]); }); } }); async function injectLocalStorage(page: Page, entries: Record&lt;string, string&gt;) { await page.addInitScript((e: Record&lt;string, string&gt;) =&gt; { for (const [key, value] of Object.entries(e)) { localStorage.setItem(key, value); } }, entries); } They repeat this for every setting type, locale, accent color, background theme, package manager, relative dates, each with a non-default value. If any combination causes a hydration mismatch on any page, the test fails. Their fixture uses Vue-specific error strings (\"Hydration completed but contains mismatches\") while mine uses React patterns. The approach is the same, only the strings you match against change. More on how E2E tests relate to unit and integration tests: If you ship an SSR app and do not check for hydration errors in your browser tests, you have one in production right now.","2026-04-09T06:11:38.114Z","019d70de-1de7-70ea-8344-aded9a900a5c","https:\u002F\u002Falexop.dev\u002Fposts\u002Fhow-to-catch-hydration-errors-in-playwright-tests-astro-nuxt-react-ssr\u002Findex.png","2026-04-06T00:00:00.000Z","how-to-catch-hydration-errors-in-playwright-tests-astro-nuxt-react-ssr","The article discusses how to identify and address hydration errors in Playwright tests, particularly in applications using SSR with frameworks like Nuxt and Astro. It explains the concept of hydration mismatches, common causes, and offers solutions to ensure consistent rendering between server and client. The focus is on maintaining a deterministic initial state to prevent hydration warnings during testing.","How to Catch Hydration Errors in Playwright Tests (Astro, Nuxt, React SSR)","2026-04-09T06:11:45.745Z","https:\u002F\u002Falexop.dev\u002Fposts\u002Fcatch-hydration-errors-playwright-tests\u002F","a61a606d501f4750cf6e2136ea2210d1a0550f673021e364846f1ce1953dd9cd",[352,353,354,355],{"color":24,"id":25,"name":26,"slug":26},{"color":24,"id":105,"name":106,"slug":106},{"color":24,"id":129,"name":130,"slug":130},{"color":24,"id":53,"name":54,"slug":54},{"content":357,"createdAt":358,"id":359,"image":360,"isAffiliate":11,"isPublished":11,"publishedAt":361,"slug":362,"sourceId":65,"sourceName":66,"sourceType":67,"summary":363,"title":364,"updatedAt":365,"url":366,"urlHash":367,"tags":368},"If you have ever built a chat thread, card feed, whiteboard label editor, or masonry layout in Vue, you have probably ended up doing something slightly gross: render text into the DOM, measure it, and then rerender or reposition everything based on that measurement. That works, but it comes with baggage: hidden measurement nodes getBoundingClientRect() and offsetHeight reads in hot paths resize loops that mix layout and app state virtualization code that needs a height before the row is even mounted Pretext is interesting because it attacks that exact problem. Instead of asking the DOM how tall wrapped text became, it prepares the text once and then lays it out against a width using cached measurements. In other words, you can know a text's height before it is even mounted so that Vue can stay focused on state and rendering while Pretext handles the text math. This is not a replacement for Vue, CSS, or the browser layout engine. It is a way to stop using the DOM as your text calculator when all you really need is line count and height. 👉 Don't quite follow? Check out the demo. It breaks down things visually for you. What Pretext actually does The core model is simple: prepare(text, font) does the expensive work once. layout(prepared, width, lineHeight) returns the wrapped height and line count for that width. That split matters. If the text and font stay the same while the available width changes, you do not need to re-measure every grapheme from scratch on every resize. You reuse the prepared value and run layout again. That makes Pretext especially compelling in UI that has lots of text blocks with widths changing over time: virtualized feeds resizable sidebars draggable canvases with labels shrink-wrapped chat bubbles card grids where item height depends on text The Vue angle Vue is already very good at state transitions. The problem is that text measurement traditionally drags you back into imperative DOM work. You start with clean reactive code: const messages = ref&lt;Message[]&gt;([]); Then layout requirements show up and suddenly you are doing things like: await nextTick(); const height = node.getBoundingClientRect().height; That is the line I would try to delete first. With Pretext, the flow looks more like this: Vue owns the text, width, and rendering state. Pretext derives height and line count from text plus width. Your list, grid, or canvas logic consumes those numbers without mounting probe elements first. That is a much cleaner separation of concerns. Install it ni @chenglou\u002Fpretext A simple Vue composable The main trick is to make prepare() depend on the text and font, but not the width. Width changes should only trigger layout(). import { computed, toValue } from &quot;vue&quot;; import { layout, prepare } from &quot;@chenglou\u002Fpretext&quot;; export function usePretextLayout(options) { const text = computed(() =&gt; toValue(options.text)); const font = computed(() =&gt; toValue(options.font)); const width = computed(() =&gt; toValue(options.width)); const lineHeight = computed(() =&gt; toValue(options.lineHeight)); const prepared = computed(() =&gt; prepare(text.value, font.value)); const result = computed(() =&gt; layout(prepared.value, Math.max(1, width.value), lineHeight.value), ); const height = computed(() =&gt; result.value.height); const lineCount = computed(() =&gt; result.value.lineCount); return { text, font, width, lineHeight, prepared, result, height, lineCount, }; } Why split it this way? Text changes should invalidate the prepared measurement. Font changes should also invalidate it. Width changes should only rerun layout. That maps nicely onto Vue's computed graph. Use it in a component Here is a minimal card example. The width is reactive, but the text measurement does not require mounting a hidden probe element just to discover its height. &lt;script setup lang=&quot;ts&quot;&gt; import { ref } from &quot;vue&quot;; import { usePretextLayout } from &quot;.\u002Fcomposables\u002FusePretextLayout&quot;; const body = ref( &quot;Pretext lets Vue apps estimate wrapped text height without measuring hidden DOM nodes first.&quot;, ); const cardWidth = ref(320); const font = ref(&quot;400 16px Inter, system-ui, sans-serif&quot;); const lineHeight = ref(24); const { height, lineCount } = usePretextLayout({ text: body, font, width: cardWidth, lineHeight, }); &lt;\u002Fscript&gt; &lt;template&gt; &lt;article class=&quot;card&quot; :style=&quot;{ width: `${cardWidth}px` }&quot;&gt; &lt;p&gt;{{ body }}&lt;\u002Fp&gt; &lt;footer&gt;{{ lineCount }} lines, {{ height }}px tall&lt;\u002Ffooter&gt; &lt;\u002Farticle&gt; &lt;\u002Ftemplate&gt; This is the important mindset shift: the card height is no longer something you discover after the browser lays out the paragraph. It is something you can derive from the same reactive inputs that already describe the UI. Where this gets really useful The simplest demo is a single card, but that is not where the real value is. The real value shows up when the old approach creates layout thrash or architectural awkwardness. 1. Virtualized lists with variable-height text Virtualization loves predictable heights. Text-heavy UIs often do not have them. Pretext gives you a better story: prepare message text when data arrives compute row height from the current column width feed that height into your virtualizer rerun layout when the list width changes That is much better than mounting off-screen rows just to measure them. import { layout, prepare, type PreparedText } from &quot;@chenglou\u002Fpretext&quot;; type Row = { id: string; body: string; prepared: PreparedText; }; const font = &quot;400 15px Inter, system-ui, sans-serif&quot;; const lineHeight = 22; const rows: Row[] = apiRows.map((row) =&gt; ({ id: row.id, body: row.body, prepared: prepare(row.body, font), })); function getRowHeight(row: Row, contentWidth: number) { return layout(row.prepared, contentWidth, lineHeight).height + 24; } That pattern fits Vue very naturally. You can prepare once when rows are normalized, then derive heights wherever your layout logic needs them. 2. Chat bubbles that size to content If your message UI wants to make decisions based on line count or wrapped height, Pretext is a cleaner primitive than &quot;render first, inspect later.&quot; Examples: deciding whether a bubble gets compact or roomy chrome estimating whether a message should collapse behind &quot;show more&quot; aligning metadata differently for one-line versus multi-line messages Those are layout decisions based on text shape, not business logic. They should not require DOM probes in every component instance. 3. Canvas, whiteboards, and design tools This is where Pretext starts to feel like a category change rather than a small optimization. Its advanced APIs, including prepareWithSegments(), layoutWithLines(), and layoutNextLine(), are designed for cases where you need more than total height: drawing each wrapped line manually finding the widest produced line routing text line by line through changing widths That is useful for labels on canvases, text around shapes, or any UI where the browser is not directly painting the final text layout for you. A practical caveat: your font string has to match reality Pretext is not guessing in the abstract. It measures text against a font declaration and then lays out against a width and line height. That means two values need to match what your UI actually renders: the font string you pass to prepare() the lineHeight you pass to layout() If your component renders with a different font weight, font family, font size, or line height than the values you gave Pretext, your estimated result will drift from the actual DOM layout. So keep the typography source of truth tight. If the component uses: .message { font: 400 16px Inter, system-ui, sans-serif; line-height: 24px; } then your measurement inputs should match those values. What Pretext does not replace This is the part worth being explicit about, because the interesting thing about Pretext is not that it replaces everything. It replaces one very specific pain point. Pretext does not replace: Vue rendering CSS text styling actual width measurement of your container browser selection, caret behavior, or editing UX the browser's final paint of the real text node You still need a width from somewhere. Sometimes that is a prop. Sometimes it comes from your layout model. Sometimes a ResizeObserver is still appropriate. The difference is that you are no longer using the DOM to answer &quot;how tall did this paragraph become?&quot; That is a much smaller and cleaner dependency on layout. When I would reach for it I would consider Pretext when all of these are true: text height or line count affects layout decisions there are many text blocks, or the calculation happens often hidden measurement DOM is making the code awkward or slow you need the answer before mounting the final row or card I would probably not reach for it when: you are rendering a handful of static paragraphs CSS alone solves the problem you only need the browser to lay out the text once and never revisit it In other words, this is not &quot;replace CSS with a library.&quot; It is &quot;stop abusing the DOM as a calculator in text-heavy reactive UIs.&quot; The broader idea What makes Pretext interesting is not just the API. It is the shift in mental model. For a long time, web developers mostly accepted that wrapped text measurement had to be a DOM problem. Pretext challenges that assumption. In a Vue app, that means some layout decisions that used to live in nextTick(), probe elements, and measurement loops can move back into pure reactive derivation. That is exactly the kind of change I like: not because it is flashy, but because it removes a category of awkward code. Summary Pretext gives Vue developers a better option for text-heavy UI where height and line count matter. You prepare text once, lay it out against width as needed, and stop relying on hidden DOM nodes to tell you what wrapped text looks like.","2026-04-08T06:47:33.045Z","019d6bd8-a30c-720e-b82e-9253cd4d2970","https:\u002F\u002Fblog.vueschool.io\u002Fwp-content\u002Fuploads\u002F2026\u002F04\u002Ffeature-v2-1.jpg","2026-04-03T20:45:27.000Z","using-pretext-in-vue-to-build-variable-height-ui-without-layout-thrash","The article discusses how to use Pretext in Vue to create variable-height UIs without the common pitfalls of layout thrashing. By preparing text measurements in advance, Pretext allows developers to avoid direct DOM manipulations for height calculations, leading to cleaner and more reactive code. This approach is particularly beneficial for UIs with dynamic text and changing widths.","Using Pretext in Vue to Build Variable-Height UI Without Layout Thrash","2026-04-08T06:47:42.561Z","https:\u002F\u002Fblog.vueschool.io\u002Fvuejs-tutorials\u002Fusing-pretext-in-vue-to-build-variable-height-ui-without-layout-thrash\u002F?friend=MOKKAPPS","75c6e75824a6e8739844c6c1bc511bbf6c5bc3302502ab9c9e1c87376385d07c",[369,370,371],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":53,"name":54,"slug":54},{"color":24,"id":28,"name":29,"slug":29},{"content":373,"createdAt":358,"id":374,"image":375,"isAffiliate":11,"isPublished":11,"publishedAt":376,"slug":377,"sourceId":65,"sourceName":66,"sourceType":67,"summary":378,"title":379,"updatedAt":380,"url":381,"urlHash":382,"tags":383},"If you have ever wired up a &lt;label for=&quot;…&quot;&gt; to an &lt;input id=&quot;…&quot;&gt;, duplicated a component twice, and suddenly had duplicate IDs in the document, you already know why “just pick an id string” does not scale. Vue 3.5 added useId(), a small Composition API helper that generates unique-per-application identifiers that stay consistent between server and client renders. Despite the casual phrase “random ids,” useId() is not a source of cryptographic randomness. It produces deterministic, stable strings that are unique within your Vue app instance. That distinction matters: you get uniqueness and SSR safety without Math.random() or global counters that fight hydration. Why useId() exists Manual patterns break down quickly: Hard-coded IDs collide when the same component is used more than once. Math.random() in setup gives different values on server and client, which can cause hydration mismatches in SSR apps. Hand-rolled incrementing counters are easy to get wrong across async boundaries or shared modules. useId() centralizes ID generation in the framework so labels, aria-* attributes, form controls, and more stay valid and predictable. Use Cases for useId() Common situations where you need a DOM-safe unique string (not cryptographic randomness): Creating unique DOM element IDs for anchor links Associating a label and input in a reusable form field component Anchoring headings for in-page navigation (table of contents) Assigning a unique id to custom tooltip or popover elements Distinguishing multiple error callouts or alerts in a single view Generating ids for ARIA attributes (aria-labelledby, aria-describedby, etc.) Marking tab panels and tab buttons with unique relationships Disambiguating ids in nested reusable components (e.g., accordions, tabs) Generating ids for form controls created at runtime (e.g., survey builders) Basic usage Import useId from vue and call it once per logical id you need in the component. Wire the returned string to id, for, or ARIA attributes as usual. &lt;script setup lang=&quot;ts&quot;&gt; import { useId } from &quot;vue&quot;; const nameFieldId = useId(); &lt;\u002Fscript&gt; &lt;template&gt; &lt;form&gt; &lt;label :for=&quot;nameFieldId&quot;&gt;Name&lt;\u002Flabel&gt; &lt;input :id=&quot;nameFieldId&quot; type=&quot;text&quot; name=&quot;name&quot; autocomplete=&quot;name&quot; \u002F&gt; &lt;\u002Fform&gt; &lt;\u002Ftemplate&gt; Each call to useId() in the same component instance receives a different id. Each instance of the component receives ids distinct from other instances. That matches what you want for accessible, reusable field groups. Multiple ids in one component Need a pair for email and password? Call useId() separately for each control (or group). &lt;script setup lang=&quot;ts&quot;&gt; import { useId } from &quot;vue&quot;; const emailId = useId(); const passwordId = useId(); &lt;\u002Fscript&gt; &lt;template&gt; &lt;div&gt; &lt;label :for=&quot;emailId&quot;&gt;Email&lt;\u002Flabel&gt; &lt;input :id=&quot;emailId&quot; type=&quot;email&quot; autocomplete=&quot;email&quot; \u002F&gt; &lt;\u002Fdiv&gt; &lt;div&gt; &lt;label :for=&quot;passwordId&quot;&gt;Password&lt;\u002Flabel&gt; &lt;input :id=&quot;passwordId&quot; type=&quot;password&quot; autocomplete=&quot;current-password&quot; \u002F&gt; &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; SSR and hydration According to the official API docs, ids from useId() are stable across server and client renders. You can use them in Nuxt, custom SSR setups, or any code path that renders on the server first without worrying that the client will “reroll” different strings during hydration. Multiple Vue apps on one page If more than one Vue application mounts on the same document, you can reduce the chance of clashes between apps by setting an id prefix on each app: import { createApp } from &quot;vue&quot;; import AdminRoot from &quot;.\u002FAdminRoot.vue&quot;; const app = createApp(AdminRoot); app.config.idPrefix = &quot;admin&quot;; app.mount(&quot;#admin-app&quot;); Use a different prefix per app instance so generated ids remain unique in the combined DOM. Important: do not call useId() inside computed() You should avoid invoking useId() inside a computed() getter. As with other composables, calling it there can cause instance conflicts because id registration is tied to component setup order. Instead, create the id at the top level of &lt;script setup&gt; (or setup()) and close over it inside computeds or methods. &lt;script setup lang=&quot;ts&quot;&gt; import { computed, useId } from &quot;vue&quot;; const fieldId = useId(); \u002F\u002F Good: fieldId is fixed for this instance; computed only derives display logic. const describedBy = computed(() =&gt; `${fieldId}-hint`); &lt;\u002Fscript&gt; When you might still use something else useId() is extremely useful on the frontend, but it is not the answer for every problem. Here are some cases where you might be tempted to reach for useId() but should not: Entity primary keys from your API—these should be generated by your database or API server Stable keys in v-for when list identity should follow data (prefer a real id from your database model) Correlation IDs for API requests—they should be unpredictable or traceable by policy; use crypto.randomUUID() or server-issued IDs instead of useId() Security-sensitive tokens—use proper random or server-issued secrets For everyday UI plumbing, though, useId() removes a whole class of duplicate-id and SSR bugs with almost no API surface. Summary To sum up, useId() is a powerful tool for generating unique ids for your application. It's a simple, composable API that helps you avoid duplicate-id and SSR bugs with almost no API surface. Uniqueness - Unique per Vue application; distinct for each component instance and on every call SSR - Produces the same id on server and client—safe for hydration Multiple apps - Use app.config.idPrefix to prevent cross-app id collisions when mounting multiple apps PitfallDo - do not call inside computed(); generate the id in setup scope","019d6bd8-a30c-720e-b82e-94ac62042df7","https:\u002F\u002Fblog.vueschool.io\u002Fwp-content\u002Fuploads\u002F2026\u002F04\u002Ffeature-v2.jpg","2026-04-03T17:04:54.000Z","generating-random-ids-in-vuejs","The article discusses the new useId() helper introduced in Vue 3.5, which generates unique identifiers for DOM elements, ensuring consistency between server and client renders. It highlights the importance of unique IDs for accessibility and SSR safety, providing examples of common use cases and basic implementation in Vue components.","Generating Random IDs in Vue.js","2026-04-08T06:47:42.447Z","https:\u002F\u002Fblog.vueschool.io\u002Fvuejs-tutorials\u002Fgenerating-random-ids-in-vue-js\u002F?friend=MOKKAPPS","9174ab0b7d5dd5024c261a80c2b1895cb25c822f78f6b49a83356425d22df98a",[384,385,386,387],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":184,"name":185,"slug":185},{"color":24,"id":105,"name":106,"slug":106},{"color":24,"id":388,"name":389,"slug":389},"019d6bd8-caef-76b9-bacd-363e31e6d2a9","accessibility",{"content":391,"createdAt":392,"id":393,"image":394,"isAffiliate":10,"isPublished":11,"publishedAt":395,"slug":396,"sourceId":397,"sourceName":398,"sourceType":16,"summary":399,"title":400,"updatedAt":401,"url":402,"urlHash":403,"tags":404},"Use a typed entry helper to infer props from each component in a component map. This is useful when rendering dynamic blocks from CMS or configuration data.","2026-04-08T06:47:51.870Z","019d6bd8-ec9b-7135-8431-bebca829dd0b","https:\u002F\u002Fmokkapps.twic.pics\u002Fmokkapps.de\u002Fvue-tips\u002Finfer-props-from-component-map\u002Fog.png","2026-03-27T00:00:00.000Z","vue-tip-infer-props-from-components-in-a-component-map","019d6bd5-57e0-742c-8de2-c0a3a1f49b60","Michael Hoffmann","This article discusses how to use a typed entry helper in Vue to infer props from components within a component map. This technique is particularly beneficial for rendering dynamic content from CMS or configuration data.","Vue Tip: Infer Props From Components in a Component Map","2026-04-08T06:47:56.189Z","https:\u002F\u002Fmokkapps.de\u002Fvue-tips\u002Finfer-props-from-component-map","e925088a3bd9b558d6a4787610f1f38c12d1aff89a40454d33e4e0e11bb49f3b",[405,406],{"color":24,"id":50,"name":51,"slug":51},{"color":24,"id":184,"name":185,"slug":185},3,20,61]