{"id":23116,"date":"2026-08-07T16:29:15","date_gmt":"2026-08-07T16:29:15","guid":{"rendered":"https:\/\/scannn.com\/introducing-kitesurf-the-agent-first-browser-that-runs-in-v8-isolates-on-cloudflare-workers\/"},"modified":"2026-08-07T16:29:15","modified_gmt":"2026-08-07T16:29:15","slug":"introducing-kitesurf-the-agent-first-browser-that-runs-in-v8-isolates-on-cloudflare-workers","status":"publish","type":"post","link":"https:\/\/scannn.com\/lv\/introducing-kitesurf-the-agent-first-browser-that-runs-in-v8-isolates-on-cloudflare-workers\/","title":{"rendered":"Introducing Kitesurf: The agent-first browser that runs in V8 isolates on Cloudflare Workers"},"content":{"rendered":"\n<div data-astro-cid-qjtkll3n=\"\">\n<p>Should we build our own browser? <\/p>\n<p>This is one of those questions that has come up every few months internally at Cloudflare for years. Unsurprisingly, it\u2019s the kind that triggers long threads with multiple reasons and persuasive arguments on why we should do it. The browser is obviously the most important software we use every day on our computers; it\u2019s arguably the operating system of the Internet. We\u2019re a company on a mission to help <a href=\"https:\/\/www.cloudflare.com\/about-overview\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>build a better Internet<\/u><\/a> \u2014 who wouldn\u2019t want to take on the challenge of building a new browser?<\/p>\n<p>But we never quite found the balance between the technical difficulty of such an endeavour and the unique problems we\u2019d be solving by doing it. And so, the idea was shelved, over and over again. Until now.<\/p>\n<p>Something magical happened: we reached a tipping point where a series of powerful technical advancements in our <a href=\"https:\/\/developers.cloudflare.com\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Developer Platform<\/u><\/a> became a reality, while the advent of AI agents and the demand for a new kind of browser became critical at the same time.<\/p>\n<p>Running <a href=\"https:\/\/developers.cloudflare.com\/workers\/runtime-apis\/webassembly\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>WebAssembly (Wasm)<\/u><\/a> in Workers is now very mature. Primitives like <a href=\"https:\/\/developers.cloudflare.com\/dynamic-workers\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>dynamic workers<\/u><\/a>, <a href=\"https:\/\/developers.cloudflare.com\/durable-objects\/api\/sqlite-storage-api\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>SQLite-based Durable Objects<\/u><\/a>, <a href=\"https:\/\/developers.cloudflare.com\/workers\/runtime-apis\/rpc\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Worker-to-worker RPC<\/u><\/a>, <a href=\"https:\/\/developers.cloudflare.com\/workers\/runtime-apis\/bindings\/service-bindings\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>service bindings<\/u><\/a>, higher <a href=\"https:\/\/developers.cloudflare.com\/workers\/runtime-apis\/nodejs\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>NodeJS compatibility<\/u><\/a> and higher <a href=\"https:\/\/developers.cloudflare.com\/workers\/platform\/limits\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>limits<\/u><\/a> open doors to much more ambitious and complex applications that were simply not possible before.<\/p>\n<p><a href=\"https:\/\/developers.cloudflare.com\/browser-run\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Browser Run<\/u><\/a>, our headless browser automation API product, has seen tremendous growth with the rise of AI. Agents need browsers in order to perform many tasks, and in many cases cannot succeed without them.<\/p>\n<p>But there&#8217;s a problem \u2014 browser engines like Chromium were built for humans, not agents, and they come with overhead that AI models simply do not need. They consume so much memory and compute that providing every agent with its own instance is prohibitively expensive, restricting large parts of the Web to only the most sophisticated and costly AI models with higher parametric knowledge, while locking out many other agentic applications.\u00a0<\/p>\n<p>We should be giving <em>all <\/em>agents a browser that excels at what\u2019s important for an AI model, even if that means being light on what\u2019s only useful for humans. For example:<\/p>\n<ul>\n<li>AI doesn\u2019t care about tabs, themes, browser extensions, or synchronization across devices. It cares about token count, context windows, scalability, performance, and costs.<\/li>\n<li>Structured, machine-readable content is important, but visual perfection, smooth 60-fps scrolling is not. Agents will be just fine if the CSS parsing is slightly off or the rendering isn\u2019t pixel perfect.<\/li>\n<li>The threat model in the context of AI using a browser is different. New problems like prompt injection and tool safety are top priorities.<\/li>\n<\/ul>\n<p>Faced with these realizations, 12 weeks ago we asked the question again: Should we build our own browser? This time the answer was unanimous: <strong>Yes!<\/strong><\/p>\n<p>Today we are announcing <strong>Kitesurf<\/strong>, a new browser that runs <strong>entirely on top of Workers<\/strong> that we built specifically for agents, available for free while in beta in <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Browser Run<\/u><\/a>.<\/p>\n<p>Kitesurf is significantly more efficient in CPU and memory consumption than Chromium for common agentic tasks like screenshots and HTML extraction. What follows is the story of how we built it. Buckle up, it\u2019s going to get technical \u2014 but we promise to keep it interesting.<\/p>\n<h2>How it started<\/h2>\n<p>Kitesurf started as many other great ideas have started at Cloudflare. Someone found something interesting, and the next thing you know they end up \u201cnerd sniping\u201d the rest of the team with a seemingly impossible but very attractive idea.<\/p>\n<p>We got the initial inspiration from <a href=\"https:\/\/github.com\/h4ckf0r0day\/obscura\" target=\"_blank\" rel=\"noopener noreferrer\"><u>obscura<\/u><\/a>, a headless engine written in Rust for AI automation that has \u201cno Chrome, no Node.js, no dependencies.\u201d<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=715&amp;h=550&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=640&amp;h=492&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=715&amp;h=550&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=750&amp;h=577&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=828&amp;h=637&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=1080&amp;h=831&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=1280&amp;h=985&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=1430&amp;h=1100&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"unnamed.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZBKVDEMTPBSVB5R6APWE6BN.png&amp;w=1920&amp;h=1476&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"1476\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"550\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>Then, with the help of an AI agent, we tried to port it to Workers. It didn&#8217;t work very well at first. But once we gave the AI a solid plan and a clear definition of success \u2014 detailed enough for the agent to loop endlessly and ask questions when needed \u2014 it did work.<br \/>Blown away by this (barely) working proof of concept, we decided to let the team cook.<\/p>\n<h3>Design decisions<\/h3>\n<p>Here are some of the design decisions we made before we started.<\/p>\n<p><strong>Tests, tests, tests<\/strong><\/p>\n<p>We knew that moving from a prototype to a full-blown browser that could actually be useful for tasks at scale in production would take a lot of work and iteration. We won\u2019t hide that using AI to accelerate the process was key. But how do you use AI in such a complex project, keeping the quality of both code and results under control without losing velocity? The answer is to provide as many tests as you can.<\/p>\n<p>Enter the <a href=\"https:\/\/github.com\/web-platform-tests\/wpt\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Web Platform Tests<\/u><\/a> (WPT), the ideal setup: an extensive suite of success criteria that gave the AI agents clear goalposts for assessing feature conformance. We curated the selection and order of features to assign to the agents, allowing humans to focus on architectural work and reviewing the agents&#8217; approaches.<\/p>\n<p>However, WPT tests only go so far: they measure conformance to <a href=\"https:\/\/www.w3.org\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>W3C<\/u><\/a> standards, not a browser&#8217;s ability to render and interact with real-world websites. To bridge this gap, we implemented a<em> <\/em>combination of integration testing and visual regression testing \u2014 it runs multistep <a href=\"https:\/\/pptr.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Puppeteer<\/u><\/a> tests on real websites against both Chromium and Kitesurf not only by comparing the assertions that it makes, but also rendering outputs at every step to highlight any unwanted differences.<\/p>\n<p><strong>Use Rust when possible<\/strong><\/p>\n<p>Cloudflare has been working on providing great support for <a href=\"https:\/\/developers.cloudflare.com\/workers\/runtime-apis\/webassembly\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>WebAssembly (Wasm)<\/u><\/a> in Workers for quite some time. This is great because we can use high-performance C, C++, and Rust packages and compile them to Wasm. If we use Emscripten (for example) and its many layers of mocked dependencies, the compiled binary can get bulky and slow.<\/p>\n<p>Instead, we opted for native Rust whenever possible and to compile directly to WebAssembly using <a href=\"https:\/\/developers.cloudflare.com\/workers\/languages\/rust\/#javascript-plumbing-wasm-bindgen\" target=\"_blank\" rel=\"noopener noreferrer\"><u>wasm-bindgen<\/u><\/a>, thus avoiding unnecessary emulation layers and running as close to the metal as possible, <a href=\"https:\/\/blog.cloudflare.com\/making-rust-workers-reliable\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>reliably<\/u><\/a>.<\/p>\n<p><strong>Exception handling<\/strong><\/p>\n<p>A browser must render the whole unreliable and sometimes hostile web without ever dropping the page it&#8217;s holding, so exception handling is more than just hygiene \u2014 it&#8217;s how the application survives bad input without just crashing outright.<\/p>\n<p>So we committed to one rule up front: any failure degrades to a blank frame or a missing element, never a dead session. Catch faults at every boundary, default to something safe and empty, and log enough to diagnose.<\/p>\n<p><strong>Isolation<\/strong><\/p>\n<p>Contrary to running a browser on your laptop (where you&#8217;re visiting sites you trust, and it&#8217;s acceptable to share some resources between them), an agent is pointed at whatever a task demands: arbitrary code from arbitrary origins.<\/p>\n<p>So we built this browser on the assumption that every page load is untrusted input and every session starts fresh. Each component is isolated and has access only to the resources strictly necessary for its function.<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img fetchpriority=\"high\" class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA9\/Lz8O3v4+Tn3+Hj5uTl7uno6ubl3t7e+vb58vH15Oft3+Pp6Ojq8e7s7uvq4ePj\/\/7\/9\/j96Oz14+jx7e7y9\/X09PPx5+rr\/\/\/\/\/\/\/\/8PX\/6\/H69fj7\/\/\/9\/f367\/P0\/\/\/\/\/\/\/\/\/P\/\/9\/z\/\/\/\/\/\/\/\/\/\/\/\/\/+v3+\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\" alt=\"\" width=\"715\" height=\"205\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=715&amp;h=205&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=640&amp;h=183&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=715&amp;h=205&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=750&amp;h=215&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=828&amp;h=237&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=1080&amp;h=310&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=1280&amp;h=367&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=1430&amp;h=410&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 3.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z89XF1C6TRP798HF5XRCW.png&amp;w=1920&amp;h=549&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"549\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"205\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>This seems like a perfect fit for Cloudflare Workers, whose <a href=\"https:\/\/developers.cloudflare.com\/workers\/reference\/security-model\/#isolation\" target=\"_blank\" rel=\"noopener noreferrer\"><u>security model<\/u><\/a> is built around isolation by design. But the platform only gets us the boundary between isolates. We still have to enforce the same principle at the application level, deciding what each component is allowed to touch and making sure nothing leaks across a page it shouldn&#8217;t.<\/p>\n<p><strong>Stateless whenever possible<\/strong><\/p>\n<p>State is what makes failure expensive \u2014 if there&#8217;s nothing to reconstruct, recovering from a crash is just starting a new one and replaying the request. A stateless component is disposable and parallel by nature: kill it the moment it stalls, run a thousand at once, and size them to demand instead of keeping things warm. That fits automation perfectly, where load arrives in bursts and the cheapest thing you can do is spin up work that costs only what it used and vanishes when it&#8217;s done. In short, <strong>wherever a component can be stateless, it should be<\/strong>.<\/p>\n<h2>How we built it<\/h2>\n<p>Armed with a good plan, extensive tests, and a good tooling environment, we were ready to get started beyond the initial proof of concept. This is Kitesurf\u2019s very high level life of a request that still holds today:<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA\/\/\/\/+\/r67+\/w6Ont7e7y9PT48vL06urq\/\/\/\/\/\/798fHz6evv7u\/09fX69PT27ezs\/\/\/\/\/\/\/\/9PX36+3z7\/H39\/f99\/f58PDw\/\/\/\/\/\/\/\/+Pn77\/H38\/T7+vv\/+\/r99fTz\/\/\/\/\/\/\/\/\/P3\/9PX6+Pn\/\/\/\/\/\/\/\/\/+Pf3\/\/\/\/\/\/\/\/\/\/\/\/+fr+\/f7\/\/\/\/\/\/\/\/\/+\/r6\/\/\/\/\/\/\/\/\/\/\/\/\/f3\/\/\/\/\/\/\/\/\/\/\/\/\/\/fz8\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/vz9\" alt=\"\" width=\"715\" height=\"497\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=715&amp;h=497&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=640&amp;h=445&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=715&amp;h=497&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=750&amp;h=521&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=828&amp;h=576&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=1080&amp;h=751&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=1280&amp;h=890&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=1430&amp;h=994&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 4.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7NX68CP9DHW93JAHJX.png&amp;w=1920&amp;h=1335&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"1335\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"497\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>Let\u2019s dive into the three main components that make Kitesurf work: the Engine, PageScript, and PageRenderer.<\/p>\n<h3>Fetching from origins<\/h3>\n<p>In order to render an untrusted web page, a browser has to fetch arbitrary assets \u2014 images, fonts, CSS, JavaScript, and Wasm files \u2014 off the Internet. This is one of the most dangerous operations a browser can do.<\/p>\n<p>Kitesurf does it through one single component, the SandboxOutbound worker, and nothing else can touch the network directly \u2014 enforced by Dynamic Workers. The Engine uses it to bootstrap the page, fetching the main document and its scripts, and PageScript fetches everything else: stylesheets, images, fonts, and the page&#8217;s own fetch() calls.<\/p>\n<p>We use SandboxOutbound to enforce <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Guides\/CORS\" target=\"_blank\" rel=\"noopener noreferrer\"><u>CORS<\/u><\/a>, inject browser-shaped headers, filter responses, and keep each page&#8217;s cookies in their own jar. Anything that fails our policy gets a 403 \u2014 each component gets precisely the network it needs and nothing more.<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA\/f\/5+Pvz7vPo6Ozl6err7Ovz7ezy6urr+f369Pn06vDq4ujn4ufu5+n26uv06urs9\/z88vj35u7u3ebr3eXz4+j66ev46+vv+v\/\/9Pr86O\/z3ufx3uf45Ov\/6+\/87+7z\/\/\/\/\/P\/\/7\/X45u335e7+6\/L\/8fX\/9PT4\/\/\/\/\/\/\/\/+v3+8fb88Pf\/9fv\/+fz\/+vr9\/\/\/\/\/\/\/\/\/\/\/\/+v3\/+v7\/\/v\/\/\/\/\/\/\/\/7\/\/\/\/\/\/\/\/\/\/\/\/\/\/v\/\/\/v\/\/\/\/\/\/\/\/\/\/\/\/\/\/\" alt=\"\" width=\"715\" height=\"395\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=715&amp;h=395&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=640&amp;h=354&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=715&amp;h=395&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=750&amp;h=414&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=828&amp;h=457&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=1080&amp;h=597&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=1280&amp;h=707&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=1430&amp;h=790&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 5.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AAR723T5KJCNQ547FAV.png&amp;w=1920&amp;h=1060&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"1060\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"395\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<h3>The Engine<\/h3>\n<p>The Engine is the only public-facing component of Kitesurf. It handles the Chrome DevTools Protocol (CDP) WebSocket and HTTP REST APIs, serves a landing page that is useful for internal testing purposes and, most importantly, stores each session state. All other components are stateless.<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA+vr\/8fL83eDx0dfo193n4+fq6Onp5OPk\/\/\/\/9\/j+5ejx2t\/p3uPp6Ozt7O3s6Ofo\/\/\/\/\/\/\/\/7vHz4+jq5evs7vLx8fPy7u3t\/\/\/\/\/\/\/\/9ff36e3v6u\/w8vb29ff38\/Pz\/\/\/\/\/\/\/\/+fr96u716e\/28vf8+Pr9+Pj5\/\/\/\/\/\/\/\/+fr\/5+v75uv88PX\/+fz\/+\/z+\/\/\/\/\/\/\/\/+Pn\/4+f\/4ef\/7vL\/+fz\/\/f7\/\/\/\/\/\/\/\/\/9\/j\/4eX\/3+X\/7fH\/+fz\/\/v\/\/\" alt=\"\" width=\"715\" height=\"457\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=715&amp;h=457&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=640&amp;h=409&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=715&amp;h=457&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=750&amp;h=479&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=828&amp;h=529&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=1080&amp;h=690&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=1280&amp;h=818&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=1430&amp;h=914&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 6.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AHPEVW2VZK1VM59SZHW.png&amp;w=1920&amp;h=1227&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"1227\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"457\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>The advantage of using CDP is client compatibility: <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/puppeteer\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Puppeteer<\/u><\/a>, Playwright, chrome-remote-interface, and the actual Chrome DevTools frontend. Point them at Kitesurf and they will all just work. This is also how Browser Run <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/cdp\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>works<\/u><\/a> (more on why this is important later).<\/p>\n<p>Contrary to what the name suggests, the Engine is actually the simplest of the Kitesurf components. The fun parts come next.<\/p>\n<h3>PageScript<\/h3>\n<p>PageScript offers a good example of the power of our new Workers features: in this case, <a href=\"https:\/\/developers.cloudflare.com\/dynamic-workers\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Dynamic Workers<\/u><\/a>.\u00a0 Kitesurf simply wouldn\u2019t have been possible before this.<\/p>\n<p>Here\u2019s a simplified diagram of how PageScript works internally.<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA+Pj78vH24uTs19ro19rt3eDx4OHu3t3k9\/r\/8fX65Ojx3OHt3uPx5Oj15ejx4eLo+P\/\/8\/r\/6PD34+vz6O737vP67vH35+nu\/f\/\/+P\/\/7\/j+7fX68\/n9+v7\/9\/r97\/H1\/\/\/\/\/\/\/\/+f\/\/9\/3\/\/v\/\/\/\/\/\/\/\/\/\/9\/n7\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\" alt=\"\" width=\"715\" height=\"324\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=715&amp;h=324&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=640&amp;h=290&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=715&amp;h=324&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=750&amp;h=340&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=828&amp;h=375&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=1080&amp;h=489&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=1280&amp;h=580&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=1430&amp;h=648&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 7.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A7714HEP695P6HKY8GD.png&amp;w=1920&amp;h=869&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"869\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"324\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>Every next page or out-of-process iframe (<a href=\"https:\/\/www.chromium.org\/developers\/design-documents\/oop-iframes\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>OOPIF<\/u><\/a>) uses Dynamic Workers to spin up a long-lived PageScript isolate that handles the page session, consisting of a clean <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/globalThis\" target=\"_blank\" rel=\"noopener noreferrer\"><u>globalThis<\/u><\/a> and the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Document_Object_Model\" target=\"_blank\" rel=\"noopener noreferrer\"><u>DOM<\/u><\/a> document object.\u00a0<\/p>\n<p>The DOM object is then populated with the results of parsing the HTML document and running all the JavaScript scripts. For parsing the HTML and the CSS we use parts of <a href=\"https:\/\/github.com\/DioxusLabs\/blitz\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Blitz<\/u><\/a>, a modular rendering engine, and <a href=\"https:\/\/github.com\/servo\/stylo\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Stylo<\/u><\/a>, Firefox\u2019s high-performance CSS parser, both written in Rust.\u00a0<\/p>\n<p>For each found &lt;script&gt; tag or .wasm file we run the JavaScript and WebAssembly code inside the same isolate.<\/p>\n<p><strong>Yes, but evals<\/strong><\/p>\n<p>What about <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/eval\" target=\"_blank\" rel=\"noopener noreferrer\"><u>evals<\/u><\/a>, you ask? Evals are trickier to handle because for <a href=\"https:\/\/developers.cloudflare.com\/workers\/runtime-apis\/web-standards\/#javascript-standards\" target=\"_blank\" rel=\"noopener noreferrer\"><u>security reasons<\/u><\/a> we still don\u2019t support eval natively in Workers. We can\u2019t spin another isolate to handle them either, because it wouldn\u2019t have access to globalThis.<\/p>\n<p>Our solution is to use <a href=\"https:\/\/boajs.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Boa JS<\/u><\/a>, an ECMAScript engine written in Rust, to compile and run on Workers. We are basically executing a runtime on top of a runtime, which doesn\u2019t seem optimal, and it isn\u2019t, but it works well enough to handle the occasional evals we find in the code. In the future, when native eval support lands in Workers, we will migrate away from Boa.<\/p>\n<h3>PageRenderer<\/h3>\n<p>This component is essentially responsible for generating the actual pixels from the computed page objects. Here\u2019s how it works:<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA\/\/\/\/+vv95ebz0tTvz9Py2N335Ob06uns\/\/\/\/+\/3+6Orz2dvu2dvy4uT26urz7Orr\/\/\/\/\/v\/\/7vD14uXv5ebz7u738vD08O3r\/\/\/\/\/\/\/\/9Pf46+7y8PD2+Pb6+vf39PLt\/\/\/\/\/\/\/\/+v798vX39\/f6\/\/3\/\/\/78+vjz\/\/\/\/\/\/\/\/\/\/\/\/9\/n8\/Pv\/\/\/\/\/\/\/\/\/\/\/75\/\/\/\/\/\/\/\/\/\/\/\/+vv\/\/f3\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/+\/z\/\/v7\/\/\/\/\/\/\/\/\/\/\/\/\/\" alt=\"\" width=\"715\" height=\"338\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=715&amp;h=338&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=640&amp;h=303&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=715&amp;h=338&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=750&amp;h=355&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=828&amp;h=391&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=1080&amp;h=511&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=1280&amp;h=605&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=1430&amp;h=676&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 8.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AR04KBNA79NHBQYG562.png&amp;w=1920&amp;h=907&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"907\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"338\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>PageRenderer works in a loop with the Engine Worker. Every time the engine needs a frame, PageRenderer gets the page object from PageScript (also known as the scene), fetches the internal fonts and images from <a href=\"https:\/\/developers.cloudflare.com\/workers\/static-assets\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Static Assets<\/u><\/a>, rasterizes everything into an image buffer, and then returns the buffer to the engine in a format that the client can display like a JPEG\/PNG or PDF.<\/p>\n<p>A big part of the magic here is handled by another Blitz module, <a href=\"https:\/\/github.com\/DioxusLabs\/blitz\/tree\/main\/packages\/blitz-paint\" target=\"_blank\" rel=\"noopener noreferrer\"><u>blitz-paint<\/u><\/a>, which in turn uses Parley for shaping the characters into glyphs, choosing fonts, and breaking text into lines.<\/p>\n<p><strong>Workers\u2019 built-in RPC system: same application, multiple isolates\u00a0<\/strong><\/p>\n<p>Cloudflare Workers have a <a href=\"https:\/\/blog.cloudflare.com\/javascript-native-rpc\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>built-in remote procedure call (RPC) system<\/u><\/a> that allows you to call methods on other Workers, pass objects between them, and call methods on those objects. You don\u2019t have to worry about API schemas, types, or authentication, you just call remoteFunction(&#8230;params) and it works. You benefit from the isolation and the resources of the remote Worker without losing the convenience of accessing all of their functions locally using JavaScript.<\/p>\n<p>Kitesurf uses this RPC system: the Engine Worker calls renderFrame() from the PageRenderer Worker over RPC using one single call and gets a PNG as the result. Because the renderer holds no page state (only a disposable cache), the engine can safely kill and relaunch it on any failed or stuck RPC call \u2014 making each render request self-contained, retryable, and its isolate cheap and throwaway.<\/p>\n<h2>Kitesurf passes 215,000+ WPT tests and growing<\/h2>\n<p>Kitesurf works. It already passes around 215,000+ <a href=\"https:\/\/github.com\/web-platform-tests\/wpt\" target=\"_blank\" rel=\"noopener noreferrer\"><u>WPT tests<\/u><\/a>, and we are adding hundreds of passing tests every week. Here you can see the evolution over time, up to the latest version since we started the project:<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA+fv59ff17e7t5ufq4+Xu4OTy2d3v0dLl+\/7++Pr68fLy6+vv5unz4Of22N\/z0NPq\/\/\/\/\/f\/\/9\/j58fL16\/D55Oz82uP40tfw\/\/\/\/\/\/\/\/\/v\/\/+fn88\/j\/7PX\/4uz\/2d\/2\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/P\/\/+P\/\/7\/b\/5en8\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/f\/\/8\/T\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/fz\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\" alt=\"\" width=\"715\" height=\"367\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=715&amp;h=367&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=640&amp;h=329&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=715&amp;h=367&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=750&amp;h=385&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=828&amp;h=425&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=1080&amp;h=554&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=1280&amp;h=657&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=1430&amp;h=734&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 9.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8A1TZBWVWDT3XZQR7AB1.png&amp;w=1920&amp;h=986&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"986\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"367\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>It\u2019s worth noting that the parts of a browser that are important to agents (e.g., CSS, DOM, HTML, selection, SVG, and XHR) have good coverage already. Even things that might not be particularly important in the context of agents, like streams, are now decently supported.<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAA\/\/\/\/\/\/376O3p1d7d1drh4eDq6ebt6enp\/\/\/\/\/\/\/96u7q1+De19zh4+Hr6uju6urp\/\/\/\/\/\/\/\/7fHs2uPg29\/j5uXt7Orw6+zr\/\/\/\/\/\/\/\/8fXw3+fj3+Tm6unw8O3z7u\/u\/\/\/\/\/\/\/\/9Pn05Ozo5Onr7u319PH38vLy\/\/\/\/\/\/\/\/+Pz46PDt6e3x8\/L6+PX79fb2\/\/\/\/\/\/\/\/+v776\/Px7PH19vX9+vj\/+Pn5\/\/\/\/\/\/\/\/+\/\/87PTy7fL29\/b\/+\/n\/+fr6\" alt=\"\" width=\"715\" height=\"584\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=715&amp;h=584&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=640&amp;h=523&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=715&amp;h=584&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=750&amp;h=613&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=828&amp;h=676&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=1080&amp;h=882&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=1280&amp;h=1045&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=1430&amp;h=1168&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 10.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AF7AB8HWYJJ0DZCRK5X.png&amp;w=1838&amp;h=1502&amp;f=webp\" data-lightbox-width=\"1838\" data-lightbox-height=\"1502\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"584\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>Performance-wise, Kitesurf is doing pretty well. Below are the medians of five Browser Run <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/quick-actions\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>quick-action<\/u><\/a> runs across a <a href=\"https:\/\/kitesurf.cloudflare.app\/corpus.txt\" target=\"_blank\" rel=\"noopener noreferrer\"><u>14-URL corpus<\/u><\/a> comparing Chromium with Kitesurf.<\/p>\n<div class=\"emdash-table-wrapper\" data-astro-cid-62p3h4ye=\"\">\n<table class=\"emdash-table\" data-astro-cid-62p3h4ye=\"\">\n<thead data-astro-cid-62p3h4ye=\"\">\n<tr data-astro-cid-62p3h4ye=\"\">\n<th data-astro-cid-62p3h4ye=\"\">\n<p><strong>Metric<\/strong><\/p>\n<\/th>\n<th data-astro-cid-62p3h4ye=\"\">\n<p><strong>Kitesurf<\/strong><\/p>\n<\/th>\n<th data-astro-cid-62p3h4ye=\"\">\n<p><strong>Chromium (warm pool)<\/strong><\/p>\n<\/th>\n<th data-astro-cid-62p3h4ye=\"\">\n<p><strong>Kitesurf, relative<\/strong><\/p>\n<\/th>\n<\/tr>\n<\/thead>\n<tbody data-astro-cid-62p3h4ye=\"\">\n<tr data-astro-cid-62p3h4ye=\"\">\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>CPU: screenshot<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>380 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>1,173 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>3.1\u00d7 less CPU than Chromium<\/p>\n<\/td>\n<\/tr>\n<tr data-astro-cid-62p3h4ye=\"\">\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>CPU: HTML extraction<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>229 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>877 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>3.8\u00d7 less than Chromium<\/p>\n<\/td>\n<\/tr>\n<tr data-astro-cid-62p3h4ye=\"\">\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>Memory: screenshot<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>57.8 MiB<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>271.0 MiB<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>4.7\u00d7 less than Chromium<\/p>\n<\/td>\n<\/tr>\n<tr data-astro-cid-62p3h4ye=\"\">\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>Memory: HTML extraction<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>39.4 MiB<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>273.7 MiB<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>7.0\u00d7 less than Chromium<\/p>\n<\/td>\n<\/tr>\n<tr data-astro-cid-62p3h4ye=\"\">\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>Wall time: screenshot<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>1,148 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>637 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p> 1.8\u00d7 slower than Chromium<\/p>\n<\/td>\n<\/tr>\n<tr data-astro-cid-62p3h4ye=\"\">\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>Wall time: HTML extraction<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>820 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>472 ms<\/p>\n<\/td>\n<td data-astro-cid-62p3h4ye=\"true\">\n<p>1.7\u00d7 slower than Chromium<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Chromium wins the stopwatch because a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Just-in-time_compilation\" target=\"_blank\" rel=\"noopener noreferrer\"><u>JIT<\/u><\/a> that has already seen this page always beats a cold software renderer \u2014 and today it does, by about 1.7x. Most of that gap comes from rasterization and JPEG\/PNG encoding, which we will keep optimizing.<\/p>\n<p>But Kitesurf wins on memory and CPU, the things that actually drive your bill, by 3-7x compared to what Chromium uses. Less memory means we can run more sessions, scale better, and fundamentally lower both our costs and yours.\u00a0<\/p>\n<h3>The most important test of all: Kitesurf runs Doom<\/h3>\n<p>We highlighted the importance of testing in our design decisions, but we all know that no matter how many tests you have, a project isn&#8217;t truly complete until Doom runs on it. Here\u2019s Kitesurf running <a href=\"https:\/\/silentspacemarine.com\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>https:\/\/silentspacemarine.com\/<\/u><\/a> from our little Doom <a href=\"https:\/\/blog.cloudflare.com\/doom-multiplayer-workers\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>experiment<\/u><\/a> a few years ago.<\/p>\n<h3>Try it today in Browser Run<\/h3>\n<p>You can try Kitesurf with Browser Run today, <strong>available for free while in beta<\/strong>, behind per-account <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/limits\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>limits<\/u><\/a>. <\/p>\n<p>The <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/cdp\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Browser Run CDP endpoint<\/u><\/a> now supports Kitesurf as an option, so your existing client <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/puppeteer\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Puppeteer<\/u><\/a>, <a href=\"https:\/\/playwright.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Playwright<\/u><\/a>, <a href=\"https:\/\/www.npmjs.com\/package\/chrome-remote-interface\" target=\"_blank\" rel=\"noopener noreferrer\"><u>chrome-remote-interface<\/u><\/a>, or any AI Agent that speaks MCP and CDP, already works. All you need to do is add the <code>browser=kitesurf<\/code> parameter to our endpoints.<\/p>\n<p>For example, to use Kitesurf with Opencode see <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/cdp\/mcp-clients\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Using with MCP clients (CDP)<\/u><\/a> in our developer documentation and use this configuration:<\/p>\n<div class=\"emdash-code\">\n<pre class=\"shiki shiki-themes github-light vesper\" tabindex=\"0\"><code><span class=\"line\"><span class=\"sk-24292e-fff\">{<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">  \"mcp\"<\/span><span class=\"sk-24292e-fff\">: {<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">    \"kitesurf\"<\/span><span class=\"sk-24292e-fff\">: {<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">      \"type\"<\/span><span class=\"sk-24292e-fff\">: <\/span><span class=\"sk-032f62-99ffe4\">\"local\"<\/span><span class=\"sk-24292e-fff\">,<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">      \"command\"<\/span><span class=\"sk-24292e-fff\">: [<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">        \"npx\"<\/span><span class=\"sk-24292e-fff\">,<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">        \"-y\"<\/span><span class=\"sk-24292e-fff\">,<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">        \"chrome-devtools-mcp@latest\"<\/span><span class=\"sk-24292e-fff\">,<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">        \"--wsEndpoint=wss:\/\/api.cloudflare.com\/client\/v4\/accounts\/&lt;ACCOUNT_ID&gt;\/browser-run\/devtools\/browser?browser=kitesurf\"<\/span><span class=\"sk-24292e-fff\">,<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">        \"--wsHeaders={<\/span><span class=\"sk-005cc5-a0a0a0\">\\\"<\/span><span class=\"sk-032f62-99ffe4\">Authorization<\/span><span class=\"sk-005cc5-a0a0a0\">\\\"<\/span><span class=\"sk-032f62-99ffe4\">:<\/span><span class=\"sk-005cc5-a0a0a0\">\\\"<\/span><span class=\"sk-032f62-99ffe4\">Bearer &lt;API_TOKEN&gt;<\/span><span class=\"sk-005cc5-a0a0a0\">\\\"<\/span><span class=\"sk-032f62-99ffe4\">}\"<\/span><\/span>\n<span class=\"line\"><span class=\"sk-24292e-fff\">      ],<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">      \"enabled\"<\/span><span class=\"sk-24292e-fff\">: <\/span><span class=\"sk-005cc5-ffc799\">true<\/span><\/span>\n<span class=\"line\"><span class=\"sk-24292e-fff\">    }<\/span><\/span>\n<span class=\"line\"><span class=\"sk-24292e-fff\">  }<\/span><\/span>\n<span class=\"line\"><span class=\"sk-24292e-fff\">}<\/span><\/span><\/code><\/pre>\n<p><button type=\"button\" class=\"code-copy-btn\" data-code-copy=\"\" aria-label=\"Copy code\" data-copy-label=\"Copy code\" data-copied-label=\"Code copied!\"><span class=\"code-copy-btn__idle\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"\/><\/svg><\/span><span class=\"code-copy-btn__done\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z\"\/><\/svg><\/span><\/button><\/div>\n<p>Another way to use Kitesurf is with <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/quick-actions\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Browser Run\u2019s Quick Actions<\/u><\/a>. Again, just add <code>browser=kitesurf<\/code> to the quick action endpoint and it will work. For example, if you need a quick screenshot from Wikipedia, this will work just fine:<\/p>\n<div class=\"emdash-code\">\n<pre class=\"shiki shiki-themes github-light vesper\" tabindex=\"0\"><code><span class=\"line\"><span class=\"sk-24292e-fff\">curl <\/span><span class=\"sk-d73a49-a0a0a0\">-<\/span><span class=\"sk-6f42c1-ffc799\">X<\/span><span class=\"sk-005cc5-fff\"> POST<\/span><span class=\"sk-24292e-fff\"> '<\/span><span class=\"sk-6f42c1-ffc799\">https<\/span><span class=\"sk-d73a49-a0a0a0\">:<\/span><span class=\"sk-6a737d-8b8b8b94\">\/\/api.cloudflare.com\/client\/v4\/accounts\/&lt;accountId&gt;\/browser-run\/screenshot?browser=kitesurf' \\<\/span><\/span>\n<span class=\"line\"><span class=\"sk-d73a49-a0a0a0\">  -<\/span><span class=\"sk-6f42c1-ffc799\">H<\/span><span class=\"sk-24292e-fff\"> '<\/span><span class=\"sk-6f42c1-ffc799\">Authorization<\/span><span class=\"sk-d73a49-a0a0a0\">:<\/span><span class=\"sk-6f42c1-ffc799\"> Bearer<\/span><span class=\"sk-24292e-fff\"> &lt;apiToken&gt;<\/span><span class=\"sk-032f62-99ffe4\">' \\<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">  -H '<\/span><span class=\"sk-6f42c1-ffc799\">Content<\/span><span class=\"sk-d73a49-a0a0a0\">-<\/span><span class=\"sk-6f42c1-ffc799\">Type<\/span><span class=\"sk-d73a49-a0a0a0\">:<\/span><span class=\"sk-24292e-fff\"> application<\/span><span class=\"sk-d73a49-a0a0a0\">\/<\/span><span class=\"sk-24292e-fff\">json<\/span><span class=\"sk-032f62-99ffe4\">' \\<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">  -d '<\/span><span class=\"sk-24292e-fff\">{<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">    \"url\"<\/span><span class=\"sk-d73a49-a0a0a0\">:<\/span><span class=\"sk-032f62-99ffe4\"> \"https:\/\/example.com\"<\/span><\/span>\n<span class=\"line\"><span class=\"sk-24292e-fff\">  }<\/span><span class=\"sk-032f62-99ffe4\">' \\<\/span><\/span>\n<span class=\"line\"><span class=\"sk-032f62-99ffe4\">  --output \"screenshot.png\"<\/span><\/span><\/code><\/pre>\n<p><button type=\"button\" class=\"code-copy-btn\" data-code-copy=\"\" aria-label=\"Copy code\" data-copy-label=\"Copy code\" data-copied-label=\"Code copied!\"><span class=\"code-copy-btn__idle\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"\/><\/svg><\/span><span class=\"code-copy-btn__done\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z\"\/><\/svg><\/span><\/button><\/div>\n<p><strong>Use the Kitesurf Playground with Chrome DevTools<\/strong><\/p>\n<p>Another option to start exploring Kitesurf is to use our <a href=\"https:\/\/kitesurf.cloudflare.app\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>public playground here<\/u><\/a>. You can type in any URL to see how Kitesurf renders the page and interact with it.<\/p>\n<p>One interesting feature of the playground is that we inject Chrome DevTools in the UI, so you can inspect expanded DOM elements, read console messages, and watch network activity while Kitesurf renders pages. More interestingly, we implemented the necessary CDP instructions for the <a href=\"https:\/\/developer.chrome.com\/docs\/devtools\/memory\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Memory panel<\/u><\/a> to report the WebAssembly footprint of each isolate, including frames, so you can gain a clear understanding of the resources each page is consuming.<\/p>\n<figure class=\"emdash-image pt-portable-image\" data-astro-cid-7nvefuv6=\"\">\n<div class=\"pt-portable-image-frame\" data-image-placeholder-frame=\"\" data-astro-cid-7nvefuv6=\"\"><img class=\"pt-portable-image-placeholder\" data-image-placeholder=\"\" src=\"data:image\/bmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAAy8\/Izc\/I0tDK2NXQ3dzZ39\/e3N3d19fX1NnS1tnS29rU4t\/a5uXj5+jo5Obn4OHi3eLd3+Pd5eXf6+rl7+7t7\/Hy7O\/x6evs4+jl5enl6+zo8fDt9PTz8\/X38PP27fDy5Onn5uvo7O7r8fLv8\/T08vT27\/L17O\/x4ebm4+jn6Ovq7e7t7vDw7O\/w6Ozu5ens3eLi3+Pk4+fn5+rq6Orr5ejq4uXn3uPl2+Dh3eLi4eXm5ejo5ejp4ubn3+Pk2+Di\" alt=\"\" width=\"715\" height=\"518\" loading=\"eager\" decoding=\"async\" aria-hidden=\"true\" data-astro-cid-7nvefuv6=\"\"\/><img loading=\"lazy\" src=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=715&amp;h=518&amp;f=webp&amp;fit=cover&amp;position=center\" srcset=\"https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=640&amp;h=464&amp;f=webp&amp;fit=cover&amp;position=center 640w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=715&amp;h=518&amp;f=webp&amp;fit=cover&amp;position=center 715w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=750&amp;h=543&amp;f=webp&amp;fit=cover&amp;position=center 750w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=828&amp;h=600&amp;f=webp&amp;fit=cover&amp;position=center 828w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=1080&amp;h=782&amp;f=webp&amp;fit=cover&amp;position=center 1080w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=1280&amp;h=927&amp;f=webp&amp;fit=cover&amp;position=center 1280w, https:\/\/blog.cloudflare.com\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=1430&amp;h=1036&amp;f=webp&amp;fit=cover&amp;position=center 1430w\" alt=\"BLOG-3466 12.png\" loading=\"lazy\" decoding=\"async\" data-image-placeholder-media=\"true\" data-lightbox-src=\"\/_image?href=https%3A%2F%2Fblog.cloudflare.com%2F_emdash%2Fapi%2Fmedia%2Ffile%2F01KZ9Z8AT4Q5YVBA4A3HPFSQGB.png&amp;w=1920&amp;h=1391&amp;f=webp\" data-lightbox-width=\"1920\" data-lightbox-height=\"1391\" data-astro-cid-7nvefuv6=\"true\" sizes=\"auto, (min-width: 715px) 715px, 100vw\" data-astro-image=\"constrained\" data-astro-image-fit=\"cover\" data-astro-image-pos=\"center\" width=\"715\" height=\"518\" class=\"pt-portable-image-media\"\/><\/div>\n<\/figure>\n<p>Check our <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Developer Documentation<\/u><\/a> for all the details on how to use Kitesurf with Browser Run.<\/p>\n<h3>When is Kitesurf better?<\/h3>\n<p>As of today, Kitesurf correctly renders pages like <a href=\"https:\/\/todomvc.com\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>TodoMVC<\/u><\/a> (vanilla, React, Vue, Angular, Preact), Wikipedia, Hacker News, the Cloudflare Blog, and much of the Cloudflare dashboard. We will keep improving Kitesurf and increasing the percentage of WPT tests that pass, to improve compatibility for more complex web pages.<\/p>\n<p>Kitesurf is great for AI agents that need to render pages but can accept the trade-offs of not using a full-featured, pixel-perfect Chromium browser. It is also excellent for automations and applications that rely on one-shot <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/quick-actions\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Quick Actions<\/u><\/a>, such as extracting content from a page or generating PDFs or screenshots, for compatible sites.<\/p>\n<p>Think of Kitesurf as an ephemeral, fully-isolated, stateless engine designed to exist only for the duration of a task, that scales well for bursty, AI-driven workloads.<\/p>\n<h3>What Kitesurf is not yet able to do<\/h3>\n<p>If you need to play video, render WebGL, negotiate a bot-challenge handshake with real TLS fingerprints, or start a ten-minute authenticated session that requires persistent state \u2014 Kitesurf isn\u2019t yet the right option. Just use Browser Run\u2019s default, which is powered by Chromium.<\/p>\n<p>The best way to know if a specific site is compatible with Kitesurf is to try it. You can do this by using the APIs or, more quickly, try it in our <a href=\"https:\/\/kitesurf.cloudflare.app\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>public playground<\/u><\/a>.<\/p>\n<p>Explore the DevTools panels and see what\u2019s happening behind the scenes, with particular attention to the console and the memory metrics.<\/p>\n<h3>Where it goes<\/h3>\n<p>Kitesurf is twelve weeks old. The first commit was in May. Here are some of the things we&#8217;re actively working on:<\/p>\n<ul>\n<li><strong>Better CDP coverage. <\/strong>Kitesurf implements a subset of the CDP protocol \u2014 enough to cover the requirements of most agents and automation tools, including robust DOM and network inspection \u2014 and we continue to expand its capabilities to be as complete as possible.<\/li>\n<li><strong>Rendering fidelity <\/strong>for screenshots and PDFs, because we know that\u00a0 LLMs can often work better from an image than from the underlying text.<\/li>\n<li><strong>WPT coverage. <\/strong>We are iterating rapidly to add more web APIs and pass more WPT tests on the road to making Kitesurf production-ready.<\/li>\n<li><strong>Efficiency<\/strong>. We keep CPU, memory, and wall time benchmarks running all the time and are working hand-in-hand with other Developer Platform teams to make Kitesurf as cost-effective and efficient as possible.<\/li>\n<\/ul>\n<h2>Final notes<\/h2>\n<p>Thank you for making it all the way here \u2014 we know this was a long and technical blog post, but hopefully an interesting one. We went into detail because we don&#8217;t take lightly how important, but also how complex, it is to build a new browser, even a very specific one.<\/p>\n<p>Kitesurf is in its early stages, but we wanted to open it up to you as soon as possible and learn from your feedback. The team will be actively improving it with frequent updates focused on performance, efficiency, and compatibility.\u00a0<\/p>\n<p>One last thing: we&#8217;re going to open source Kitesurf once we&#8217;re ready \u2014 hopefully soon. Our goal is to let any customer deploy their own version of Kitesurf on their own accounts, if they want to.<\/p>\n<p>So give it a try in the <a href=\"https:\/\/kitesurf.cloudflare.app\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>playground<\/u><\/a>, keep an eye on our <a href=\"https:\/\/developers.cloudflare.com\/browser-run\/changelog\/\" target=\"_blank\" rel=\"noopener noreferrer\"><u>changelog<\/u><\/a>, and come chat with the team on <a href=\"https:\/\/discord.com\/invite\/cloudflaredev\" target=\"_blank\" rel=\"noopener noreferrer\"><u>Discord<\/u><\/a>. Share your experience and send us feedback; we\u2019ll be listening.<\/p>\n<\/div>\n<p><a href=\"https:\/\/blog.cloudflare.com\/kitesurf\/?utm_source=tldrai\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Should we build our own browser? This is one of those questions that has come up every few months internally at Cloudflare for years. Unsurprisingly, it\u2019s the kind that triggers long threads with multiple reasons and persuasive arguments on why we should do it. The browser is obviously the most important software we use every [&hellip;]<\/p>\n","protected":false},"author":16,"featured_media":23117,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[143],"tags":[],"class_list":["post-23116","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai"],"_links":{"self":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/posts\/23116","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/comments?post=23116"}],"version-history":[{"count":0,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/posts\/23116\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/media\/23117"}],"wp:attachment":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/media?parent=23116"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/categories?post=23116"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/tags?post=23116"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}