
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Thu, 09 Apr 2026 06:22:29 GMT</lastBuildDate>
        <item>
            <title><![CDATA[More NPM packages on Cloudflare Workers: Combining polyfills and native code to support Node.js APIs]]></title>
            <link>https://blog.cloudflare.com/more-npm-packages-on-cloudflare-workers-combining-polyfills-and-native-code/</link>
            <pubDate>Mon, 09 Sep 2024 21:00:00 GMT</pubDate>
            <description><![CDATA[ Workers now supports more NPM packages and Node.js APIs using an overhauled hybrid compatibility layer. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Today, we are excited to announce a preview of <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>improved Node.js compatibility</u></a> for Workers and Pages. Broader compatibility lets you use more NPM packages and take advantage of the JavaScript ecosystem when writing your Workers.</p><p>Our newest version of Node.js compatibility combines the best features of our previous efforts. <a href="https://workers.cloudflare.com/"><u>Cloudflare Workers</u></a> have supported Node.js in some form for quite a while. We first announced polyfill support in <a href="https://blog.cloudflare.com/node-js-support-cloudflare-workers"><u>2021</u></a>, and later <a href="https://blog.cloudflare.com/workers-node-js-asynclocalstorage"><u>built-in support for parts of the Node.js API</u></a> that has <a href="https://blog.cloudflare.com/workers-node-js-apis-stream-path"><u>expanded</u></a> over time.</p><p>The latest changes make it even better:</p><ul><li><p>You can use far more <a href="https://en.wikipedia.org/wiki/Npm"><u>NPM</u></a> packages on Workers.</p></li><li><p>You can use packages that do not use the <code>node</code>: prefix to import Node.js APIs</p></li><li><p>You can use <a href="https://workers-nodejs-compat-matrix.pages.dev/"><u>more Node.js APIs on Workers</u></a>, including most methods on <a href="https://nodejs.org/docs/latest/api/async_hooks.html"><code><u>async_hooks</u></code></a>, <a href="https://nodejs.org/api/buffer.html"><code><u>buffer</u></code></a>, <a href="https://nodejs.org/api/dns.html"><code><u>dns</u></code></a>, <a href="https://nodejs.org/docs/latest/api/os.html"><code><u>os</u></code></a>, and <a href="https://nodejs.org/docs/latest/api/events.html"><code><u>events</u></code></a>. Many more, such as <a href="https://nodejs.org/api/fs.html"><code><u>fs</u></code></a> or <a href="https://nodejs.org/docs/latest/api/process.html"><code><u>process</u></code></a> are importable with mocked methods.</p></li></ul><p>To give it a try, add the following flag to <code>wrangler.toml</code>, and deploy your Worker with <a href="https://developers.cloudflare.com/workers/wrangler/"><u>Wrangler</u></a>:</p><p><code>compatibility_flags = ["nodejs_compat_v2"]</code></p><p>Packages that could not be imported with <code>nodejs_compat</code>, even as a dependency of another package, will now load. This includes popular packages such as <a href="https://www.npmjs.com/package/body-parser">body-parser</a>, <a href="https://www.npmjs.com/package/jsonwebtoken">jsonwebtoken</a>, {}<a href="https://www.npmjs.com/package/got">got</a>, <a href="https://www.npmjs.com/package/passport">passport</a>, <a href="https://www.npmjs.com/package/md5">md5</a>, <a href="https://www.npmjs.com/package/knex">knex</a>, <a href="https://www.npmjs.com/package/mailparser">mailparser</a>, <a href="https://www.npmjs.com/package/csv-stringify">csv-stringify</a>, <a href="https://www.npmjs.com/package/cookie-signature">cookie-signature</a>, <a href="https://www.npmjs.com/package/stream-slice">stream-slice</a>, and many more.</p><p>This behavior will soon become the default for all Workers with the <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>existing nodejs_compat compatibility flag</u></a> enabled, and a <a href="https://developers.cloudflare.com/workers/configuration/compatibility-dates/"><u>compatibility date</u></a> of 2024-09-23 or later. As you experiment with improved Node.js compatibility, share your feedback by <a href="https://github.com/cloudflare/workers-sdk/issues/new?assignees=&amp;labels=bug&amp;projects=&amp;template=bug-template.yaml&amp;title=%F0%9F%90%9B+BUG%3A"><u>opening an issue on GitHub</u></a>.</p>
    <div>
      <h3>Workerd is not Node.js</h3>
      <a href="#workerd-is-not-node-js">
        
      </a>
    </div>
    <p>To understand the latest changes, let’s start with a brief overview of how the Workers runtime differs from <a href="https://nodejs.org/"><u>Node.js</u></a>.</p><p>Node.js was built primarily for services run directly on a host OS and pioneered server-side JavaScript. Because of this, it includes functionality necessary to interact with the host machine, such as <a href="https://nodejs.org/api/process.html"><u>process</u></a> or <a href="https://nodejs.org/api/fs.html"><u>fs</u></a>, and a variety of utility modules, such as <a href="https://nodejs.org/api/crypto.html"><u>crypto</u></a>.</p><p>Cloudflare Workers run on an open source JavaScript/Wasm runtime called <a href="https://github.com/cloudflare/workerd"><u>workerd</u></a>. While both Node.js and workerd are built on <a href="https://v8.dev/"><u>V8</u></a>, workerd is <a href="https://blog.cloudflare.com/cloud-computing-without-containers"><u>designed to run untrusted code in shared processes</u></a>, exposes <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/"><u>bindings</u></a> for interoperability with other Cloudflare services, including <a href="https://blog.cloudflare.com/javascript-native-rpc"><u>JavaScript-native RPC</u></a>, and uses <a href="https://blog.cloudflare.com/introducing-the-wintercg"><u>web-standard APIs</u></a> whenever possible.</p><p>Cloudflare <a href="https://blog.cloudflare.com/introducing-the-wintercg/"><u>helped establish</u></a> <a href="https://wintercg.org/"><u>WinterCG</u></a>, the Web-interoperable Runtimes Community Group to improve interoperability of JavaScript runtimes, both with each other and with the web platform. You can build many applications using only web-standard APIs, but what about when you want to import dependencies from NPM that rely on Node.js APIs?</p><p>For example, if you attempt to import <a href="https://www.npmjs.com/package/pg"><u>pg</u></a>, a PostgreSQL driver, without Node.js compatibility turned on…</p>
            <pre><code>import pg from 'pg'</code></pre>
            <p>You will see the following error when you run <a href="https://developers.cloudflare.com/workers/wrangler/commands/#dev"><u>wrangler dev</u></a> to build your Worker:</p>
            <pre><code>✘ [ERROR] Could not resolve "events"
    ../node_modules/.pnpm/pg-cloudflare@1.1.1/node_modules/pg-cloudflare/dist/index.js:1:29:
      1 │ import { EventEmitter } from 'events';
        ╵                              ~~~~~~~~
  The package "events" wasn't found on the file system but is built into node.</code></pre>
            <p>This happens because the pg package imports the <a href="https://nodejs.org/api/events.html"><u>events module</u></a> from Node.js, which is not provided by workerd by default.</p><p>How can we enable this?</p>
    <div>
      <h3>Our first approach – build-time polyfills</h3>
      <a href="#our-first-approach-build-time-polyfills">
        
      </a>
    </div>
    <p>Polyfills are code that add functionality to a runtime that does not natively support it. They are often added to provide modern JavaScript functionality to older browsers, but can be used for server-side runtimes as well.</p><p>In 2022, we <a href="https://github.com/cloudflare/workers-sdk/pull/869"><u>added functionality to Wrangler</u></a> that injected polyfill implementations of some Node.js APIs into your Worker if you set <code>node_compat = true</code> in your wrangler.toml. For instance, the following code would work with this flag, but not without:</p>
            <pre><code>import EventEmitter from 'events';
import { inherits } from 'util';</code></pre>
            <p>These polyfills are essentially just additional JavaScript code added to your Worker by <a href="https://developers.cloudflare.com/workers/wrangler/"><u>Wrangler</u></a> when deploying the Worker. This behavior is enabled by <a href="https://www.npmjs.com/package/@esbuild-plugins/node-globals-polyfill"><code><u>@esbuild-plugins/node-globals-polyfill</u></code></a> which in itself uses <a href="https://github.com/ionic-team/rollup-plugin-node-polyfills/"><code><u>rollup-plugin-node-polyfills</u></code></a>.</p><p>This allows you to import and use some NPM packages, such as pg. However, many modules cannot be polyfilled with fast enough code or cannot be polyfilled at all.</p><p>For instance, <a href="https://nodejs.org/api/buffer.html"><u>Buffer</u></a> is a common Node.js API used to handle binary data. Polyfills exist for it, but JavaScript is often not optimized for the operations it performs under the hood, such as <code>copy</code>, <code>concat</code>, substring searches, or transcoding. While it is possible to implement in pure JavaScript, it could be far faster if the underlying runtime could use primitives from different languages. Similar limitations exist for other popular APIs such as <a href="https://nodejs.org/api/crypto.html"><u>Crypto</u></a>, <a href="https://nodejs.org/api/async_context.html"><u>AsyncLocalStorage</u></a>, and <a href="https://nodejs.org/api/stream.html"><u>Stream</u></a>.</p>
    <div>
      <h3>Our second approach – native support for some Node.js APIs in the Workers runtime</h3>
      <a href="#our-second-approach-native-support-for-some-node-js-apis-in-the-workers-runtime">
        
      </a>
    </div>
    <p>In 2023, we <a href="https://blog.cloudflare.com/workers-node-js-asynclocalstorage"><u>started adding</u></a> a subset of Node.js APIs directly to the Workers runtime. You can enable these APIs by adding the <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>nodejs_compat compatibility flag</u></a> to your Worker, but you cannot use polyfills with <code>node_compat = true</code> at the same time.</p><p>Also, when importing Node.js APIs, you must use the <code>node</code>: prefix:</p>
            <pre><code>import { Buffer } from 'node:buffer';</code></pre>
            <p>Since these Node.js APIs are built directly into the Workers runtime, they can be <a href="https://github.com/cloudflare/workerd/blob/main/src/workerd/api/node/buffer.c%2B%2B"><u>written in C++</u></a>, which allows them to be faster than JavaScript polyfills. APIs like <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/asynclocalstorage/"><u>AsyncLocalStorage</u></a>, which cannot be polyfilled without safety or performance issues, can be provided natively.</p><p>Requiring the <code>node: </code>prefix made imports more explicit and aligns with modern Node.js conventions. Unfortunately, existing NPM packages may import modules without <code>node:</code>. For instance, revisiting the example above, if you import the popular package <code>pg</code> in a Worker with the <code>nodejs_compat</code> flag, you still see the following error:</p>
            <pre><code>✘ [ERROR] Could not resolve "events"
    ../node_modules/.pnpm/pg-cloudflare@1.1.1/node_modules/pg-cloudflare/dist/index.js:1:29:
      1 │ import { EventEmitter } from 'events';
        ╵                              ~~~~~~~~
  The package "events" wasn't found on the file system but is built into node.</code></pre>
            <p>Many NPM packages still didn’t work in Workers, even if you enabled the <code>nodejs_compat</code> compatibility flag. You had to choose between a smaller set of performant APIs, exposed in a way that many NPM packages couldn’t access, or a larger set of incomplete and less performant APIs. And APIs like <code>process</code> that are exposed as globals in Node.js could still only be accessed by importing them as modules.</p>
    <div>
      <h3>The new approach: a hybrid model</h3>
      <a href="#the-new-approach-a-hybrid-model">
        
      </a>
    </div>
    <p>What if we could have the best of both worlds, and it just worked?</p><ul><li><p>A subset of Node.js APIs implemented directly in the Workers Runtime </p></li><li><p>Polyfills for the majority of other Node.js APIs</p></li><li><p>No <code>node</code>: prefix required</p></li><li><p>One simple way to opt-in</p></li></ul><p>Improved Node.js compatibility does just that.</p><p>Let’s take a look at two lines of code that look similar, but now act differently under the hood when <code>nodejs_compat_v2</code> is enabled:</p>
            <pre><code>import { Buffer } from 'buffer';  // natively implemented
import { isIP } from 'net'; // polyfilled</code></pre>
            <p>The first line imports <code>Buffer</code> from a <a href="https://github.com/cloudflare/workerd/blob/main/src/node/internal/internal_buffer.ts"><u>JavaScript module</u></a> in workerd that is backed by <a href="https://github.com/cloudflare/workerd/blob/main/src/workerd/api/node/buffer.c%2B%2B"><code><u>C++ code</u></code></a>. Various other Node.js modules are similarly implemented in a combination of Typescript and C++, including <a href="https://github.com/cloudflare/workerd/blob/main/src/workerd/api/node/async-hooks.h"><code><u>AsyncLocalStorage</u></code></a> and <a href="https://github.com/cloudflare/workerd/blob/main/src/workerd/api/node/crypto.h"><code><u>Crypto</u></code></a>. This allows for highly performant code that matches Node.js behavior.</p><p>Note that the <code>node:</code> prefix is not needed when importing <code>buffer</code>, but the code would also work with <code>node:buffer</code>.</p><p>The second line imports <code>net</code> which Wrangler automatically polyfills using a library called <a href="https://github.com/unjs/unenv"><u>unenv</u></a>. Polyfills and built-in runtime APIs now work together.</p><p>Previously, when you set <code>node_compat = true</code>, Wrangler added polyfills for every Node.js API that it was able to, even if neither your Worker nor its dependencies used that API. When you enable the <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>nodejs_compat_v2 compatibility flag</u></a>, Wrangler only adds polyfills for Node.js APIs that your Worker or its dependencies actually use. This results in small Worker sizes, even with polyfills.</p><p>For some Node.js APIs, there is not yet native support in the Workers runtime nor a polyfill implementation. In these cases, unenv “mocks” the interface. This means it adds the module and its methods to your Worker, but calling methods of the module will either do nothing or will throw an error with a message like:</p><p><code>[unenv] &lt;method name&gt; is not implemented yet!</code></p><p>This is more important than it might seem. Because if a Node.js API is “mocked”, NPM packages that depend on it can still be imported. Consider the following code:</p>
            <pre><code>// Package name: my-module

import fs from "fs";

export function foo(path) {
  const data = fs.readFileSync(path, 'utf8');
  return data;
}

export function bar() {
  return "baz";
}
</code></pre>
            
            <pre><code>import { bar } from "my-module"

bar(); // returns "baz"
foo(); // throws readFileSync is not implemented yet!
</code></pre>
            <p>Previously, even with the <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>existing nodejs_compat compatibility flag</u></a> enabled, attempting to import my-module would fail at build time, because the <code>fs</code> module could not be resolved. Now, the <code>fs</code> module can be resolved, methods that do not rely on an unimplemented Node.js API work, and methods that do throw a more specific error – a runtime error that a specific Node.js API method is not yet supported, rather than a build-time error that the module could not be resolved.</p><p>This is what enables some packages to transition from “doesn’t even load on Workers” to, “loads, but with some unsupported methods”.</p>
    <div>
      <h3>Still missing an API from Node.js? Module aliasing to the rescue</h3>
      <a href="#still-missing-an-api-from-node-js-module-aliasing-to-the-rescue">
        
      </a>
    </div>
    <p>Let’s say you need an NPM package to work on Workers that relies on a Node.js API that isn’t yet implemented in the Workers runtime or as a polyfill in unenv. You can use <a href="https://developers.cloudflare.com/workers/wrangler/configuration/#module-aliasing"><u>module aliasing</u></a> to implement just enough of that API to make things work.</p><p>For example, let’s say the NPM package you need to work calls <a href="https://nodejs.org/api/fs.html#fsreadfilepath-options-callback"><u>fs.readFile</u></a>. You can alias the fs module by adding the following to your Worker’s wrangler.toml:</p>
            <pre><code>[alias]
"fs" = "./fs-polyfill"</code></pre>
            <p>Then, in the fs-polyfill.js file, you can define your own implementation of any methods of the fs module:</p>
            <pre><code>export function readFile() {
  console.log("readFile was called");
  // ...
}
</code></pre>
            <p>Now, the following code, which previously threw the error message “[unenv] readFile is not implemented yet!”, runs without errors:</p>
            <pre><code>import { readFile } from 'fs';

export default {
  async fetch(request, env, ctx) {
    readFile();
    return new Response('Hello World!');
  },
};
</code></pre>
            <p>You can also use module aliasing to provide an implementation of an NPM package that does not work on Workers, even if you only rely on that NPM package indirectly, as a dependency of one of your Worker's dependencies.</p><p>For example, some NPM packages, such as <a href="https://www.npmjs.com/package/cross-fetch"><u>cross-fetch</u></a>, depend on <a href="https://www.npmjs.com/package/node-fetch"><u>node-fetch</u></a>, a package that provided a polyfill of the <a href="https://developers.cloudflare.com/workers/runtime-apis/fetch/"><u>fetch() API</u></a> before it was built into Node.js. The node-fetch package isn't needed in Workers, because the fetch() API is provided by the Workers runtime. And node-fetch doesn't work on Workers, because it relies on currently unsupported Node.js APIs from the <a href="https://nodejs.org/api/http.html"><u>http</u></a> and <a href="https://nodejs.org/api/https.html"><u>https</u></a> modules.</p><p>You can alias all imports of node-fetch to instead point directly to the fetch() API that is built into the Workers runtime using the popular <a href="https://github.com/SukkaW/nolyfill"><u>nolyfill</u></a> package:</p>
            <pre><code>[alias]
"node-fetch" = "./fetch-nolyfill"</code></pre>
            <p>All your replacement module needs to do in this case is to re-export the fetch API that is built into the Workers runtime:</p>
            <pre><code>export default fetch;</code></pre>
            
    <div>
      <h3>Contributing back to unenv</h3>
      <a href="#contributing-back-to-unenv">
        
      </a>
    </div>
    <p>Cloudflare is actively contributing to unenv. We think unenv is solving the problem of cross-runtime compatibility the right way — it adds only the necessary polyfills to your application, based on what APIs you use and what runtime you target. The project supports a variety of runtimes beyond workerd and is already used by other popular projects including <a href="https://nuxt.com/"><u>Nuxt</u></a> and <a href="https://nitro.unjs.io/"><u>Nitro</u></a>. We want to thank <a href="https://github.com/pi0"><u>Pooya Parsa</u></a> and the unenv maintainers and encourage others in the ecosystem to adopt or contribute.</p>
    <div>
      <h3>The path forward</h3>
      <a href="#the-path-forward">
        
      </a>
    </div>
    <p>Currently, you can enable improved Node.js compatibility by setting the <code>nodejs_compat_v2</code> flag in <code>wrangler.toml</code>. We plan to make the new behavior the default when using the <code>nodejs_compat</code> flag on September 23rd. This will require updating your <a href="https://developers.cloudflare.com/workers/configuration/compatibility-dates/"><code><u>compatibility_date</u></code></a>.</p><p>We are excited about the changes coming to Node.js compatibility, and encourage you to try it today. <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>See the documentation</u></a> on how to opt-in for your Workers, and please send feedback and report bugs <a href="https://github.com/cloudflare/workers-sdk/issues/new?assignees=&amp;labels=bug&amp;projects=&amp;template=bug-template.yaml&amp;title=%F0%9F%90%9B+BUG%3A"><u>by opening an issue</u></a>. Doing so will help us identify any gaps in support and ensure that as much of the Node.js ecosystem as possible runs on Workers.</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Node.js]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[JavaScript]]></category>
            <guid isPermaLink="false">3zICVbgdxrLByG4g2Dsddy</guid>
            <dc:creator>James M Snell</dc:creator>
            <dc:creator>Igor Minar</dc:creator>
            <dc:creator>James Culveyhouse</dc:creator>
            <dc:creator>Mike Nomitch</dc:creator>
        </item>
        <item>
            <title><![CDATA[Blazing fast development with full-stack frameworks and Cloudflare]]></title>
            <link>https://blog.cloudflare.com/blazing-fast-development-with-full-stack-frameworks-and-cloudflare/</link>
            <pubDate>Fri, 05 Apr 2024 13:00:44 GMT</pubDate>
            <description><![CDATA[ You can now use your framework’s development server while accessing D1 databases, R2 object stores, AI models, and more. Iterate locally in milliseconds to build sophisticated web apps that run on Cloudflare ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Hello web developers! <a href="/making-cloudflare-for-web">Last year we released</a> a slew of improvements that made deploying web applications on Cloudflare much easier, and in response we’ve seen a large growth of Astro, Next.js, Nuxt, Qwik, Remix, SolidStart, SvelteKit, and other web apps hosted on Cloudflare. Today we are announcing major improvements to our integration with these web frameworks that makes it easier to develop sophisticated applications that use our <a href="https://www.cloudflare.com/developer-platform/products/d1/">D1 SQL database</a>, <a href="https://developers.cloudflare.com/r2/">R2</a> object store, <a href="https://developers.cloudflare.com/ai/">AI</a> models, and other powerful features of <a href="https://www.cloudflare.com/developer-platform/">Cloudflare’s developer platform.</a></p><p>In the past, if you wanted to develop a web framework-powered application with D1 and run it locally, you’d have to build a production build of your application, and then run it locally using `wrangler pages dev`. While this worked, each of your code iterations would take seconds, or tens of seconds for big applications. Iterating using production builds is simply too slow, pulls you out of the <a href="https://en.wikipedia.org/wiki/Flow_(psychology)">flow</a>, and doesn’t allow you to take advantage of all the DX optimizations that framework authors have put a lot of hard work into. This is changing today!</p><p>Our goal is to integrate with web frameworks in the most natural way possible, without developers having to learn and adopt significant workflow changes or custom APIs when deploying their app to Cloudflare. Whether you are a Next.js developer, a Nuxt developer, or prefer another framework, you can now keep on using the blazing fast local development workflow familiar to you, and ship your application on Cloudflare.</p><p>All full-stack web frameworks come with a local development server (dev server) that is custom tailored to the framework and often provides an excellent development experience, with only one exception — they don't natively support some important features of Cloudflare’s development platform, especially our <a href="https://www.cloudflare.com/developer-platform/products/#storage">storage solutions</a>.</p><p>So up until recently, you had to make a tough choice. You could use the framework-specific dev server to develop your application, but forgo access to many of Cloudflare’s features. Alternatively, you could take full advantage of Cloudflare’s platform including various resources like D1 or R2<a href="https://developers.cloudflare.com/workers/configuration/bindings/">, but you would have to give up using the framework specific developer tooling. In that case, your iteration cycle would slow down, and it would take seconds rather than milliseconds for you to see results of your code changes in the browser.</a> But not anymore! Let’s take a look.</p>
    <div>
      <h3>Let’s build an application</h3>
      <a href="#lets-build-an-application">
        
      </a>
    </div>
    <p>Let’s create a new application using <a href="https://developers.cloudflare.com/pages/get-started/c3/">C3</a> — our create-cloudflare CLI. We could use any npm client of our choice (pnpm anyone?!?), but to keep things simple in this post, we’ll stick with the default npm client. To get started, just run:</p><p><code>$ npm create cloudflare@latest</code></p><p>Provide a name for your app, or stick with the randomly generated one. Then select the “Website or web app” category, and pick a full-stack framework of your choice. We support many: <a href="https://astro.build/">Astro</a>, <a href="https://nextjs.org/">Next.js</a>, <a href="https://nuxt.com/">Nuxt</a>, <a href="https://qwik.dev/">Qwik</a>, <a href="https://remix.run/">Remix</a>, <a href="https://start.solidjs.com/getting-started/what-is-solidstart">SolidStart</a>, and <a href="https://kit.svelte.dev/">SvelteKit</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/pa9natISsY3icyIUpu9Ul/cd6aa0baebe85d1b0a85eaba684d66e6/Screenshot-2024-03-28-at-7.25.10-AM.png" />
            
            </figure><p>Since C3 delegates the application scaffolding to the latest version of the framework-specific CLI, you will scaffold the application exactly as the framework authors intended without missing out on any of the framework features or options. C3 then adds to your application everything necessary for integrating and deploying to Cloudflare so that you don’t have to configure it yourself.</p><p>With our application scaffolded, let’s get it to display a list of products stored in a database with just a few steps. First, we add the configuration for our database to our <a href="https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases">wrangler.toml</a> config file:</p>
            <pre><code>[[d1_databases]]
binding = "DB"
database_name = "blog-products-db"
database_id = "XXXXXXXXXXXXXXXX"</code></pre>
            <p>Yes, that’s right! You can now configure your <a href="https://developers.cloudflare.com/workers/configuration/bindings/">bound resources</a> via the <a href="https://developers.cloudflare.com/pages/functions/wrangler-configuration/">wrangler.toml file</a>, even for full-stack apps deployed to Pages. We’ll share much more about configuration enhancements to Pages in a <a href="/browser-rendering-api-ga-rolling-out-cloudflare-snippets-swr-and-bringing-workers-for-platforms-to-our-paygo-plans/">dedicated announcement</a>.</p><p>Now let’s create a simple schema.sql file representing our database schema:</p>
            <pre><code>CREATE TABLE products(product_id INTEGER PRIMARY KEY, name TEXT, price INTEGER);
INSERT INTO products (product_id, name, price) VALUES (1, 'Apple', 250), (2, 'Banana', 100), (3, 'Cherry', 375);</code></pre>
            <p>And initialize our database:</p><p><code>$ npx wrangler d1 execute blog-products-db --local --file schema.sql</code></p><p>Notice that we used the <code>–local</code> flag of <a href="https://developers.cloudflare.com/workers/wrangler/commands/#execute"><code>wrangler d1 execute</code></a> to apply the changes to our local D1 database. This is the database that our dev server will connect to.</p><p>Next, if you use TypeScript, let TypeScript know about your database by running:</p><p><code>$ npm run build-cf-types</code></p><p>This command is preconfigured for all full-stack applications created via C3 and executes <a href="https://developers.cloudflare.com/workers/wrangler/commands/#types"><code>wrangler types</code></a> to update the interface of Cloudflare’s environment containing all configured bindings.</p><p>We can now start the dev server provided by your framework via a handy shortcut:</p><p><code>$ npm run dev</code></p><p>This shortcut will start your framework’s dev server, whether it’s powered by <a href="https://nextjs.org/docs/app/api-reference/next-cli#development">next dev</a>, <a href="https://nitro.unjs.io/">nitro</a>, or <a href="https://vitejs.dev/">vite</a>.</p><p>Now to access our database and list the products, we can now use a framework specific approach. For example, in a Next.js application that uses the App router, we could update <code>app/api/hello/route.ts</code> with the following:</p>
            <pre><code>const db = getRequestContext().env.DB;
 const productsResults = await db.prepare('SELECT * FROM products').all();
 return Response.json(productsResults.results);</code></pre>
            <p>Or in a Nuxt application, we can create a <code>server/api/hello.ts</code> file and populate it with:</p>
            <pre><code>export default defineEventHandler(async ({ context }) =&gt; {
   const db = context.cloudflare.env.DB;
   const productsResults = await db.prepare('SELECT * FROM products').all();
   return productsResults.results;
 });</code></pre>
            <p>Assuming that the framework dev server is running on port 3000, you can test the new API route in either framework by navigating to <a href="http://localhost:3000/api/hello">http://localhost:3000/api/hello</a>. For simplicity, we picked API routes in these examples, but the same applies to any UI-generating routes as well.</p><p>Each web framework has its own way to define routes and pass contextual information about the request throughout the application, so how you access your databases, object stores, and other resources will depend on your framework. You can read our updated full-stack framework guides to learn more:</p><ul><li><p><a href="https://developers.cloudflare.com/pages/framework-guides/deploy-an-astro-site/">Astro guide</a></p></li><li><p><a href="https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-nextjs-site/">Next.js guide</a></p></li><li><p><a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-nuxt-site/">Nuxt guide</a></p></li><li><p><a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-qwik-site/">Qwik guide</a></p></li><li><p><a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-remix-site/">Remix guide</a></p></li><li><p><a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-solid-site/">SolidStart guide</a></p></li><li><p><a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-site/">SvelteKit guide</a></p></li></ul><p>Now that you know how to access Cloudflare’s resources in the framework of your choice, everything else you know about your framework remains the same. You can now develop your application locally, using the development server optimized for your framework, which often includes support for hot module replacement (HMR), custom dev tools, enhanced debugging support and more, all while still benefiting from Cloudflare-specific APIs and features. Win-win!</p>
    <div>
      <h3>What has actually changed to enable these development workflows?</h3>
      <a href="#what-has-actually-changed-to-enable-these-development-workflows">
        
      </a>
    </div>
    <p>To decrease the development latency and preserve the custom framework-specific experiences, we needed to enable web frameworks and their dev servers to integrate with wrangler and miniflare in a seamless, almost invisible way.</p><p><a href="https://miniflare.dev/">Miniflare</a> is a key component in this puzzle. It is our local simulator for Cloudflare-specific resources, which is powered by <a href="https://github.com/cloudflare/workerd">workerd</a>, our JavaScript (JS) runtime. By relying on workerd, we ensure that Cloudflare’s JavaScript APIs run locally in a way that faithfully simulates our production environment. The trouble is that framework dev servers already rely on Node.js to run the application, so bringing another JS runtime into the mix breaks many assumptions in how these dev servers have been architected.</p><p>Our team however came up with an interesting approach to bridging the gap between these two JS runtimes. We call it the <a href="https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy">getPlatformProxy()</a> API, which is now part of wrangler and is super-powered by <a href="https://github.com/cloudflare/miniflare/pull/639">miniflare’s magic proxy</a>. This API exposes a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">JS proxy object</a> that behaves just like the usual <a href="https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#parameters">Workers env object</a> containing all bound resources. The proxy object enables code from Node.js to transparently invoke JavaScript code running in workerd, as well access Cloudflare-specific runtime APIs.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1MPXvdyK3eYsY3YKnOC8co/480cdd00d7a921ecb65eb738caf17ff2/pasted-image-0--6-.png" />
            
            </figure><p>With this bridge between the Node.js and workerd runtimes, your application can now access Cloudflare simulators for D1, R2, KV and other storage solutions directly while running in a dev server powered by Node.js. Or you could even write an Node.js script to do the same:</p>
            <pre><code> import {getPlatformProxy} from 'wrangler';


 const {env} = getPlatformProxy();
 console.dir(env);
 const db = env.DB;


 // Now let’s execute a DB query that runs in a local D1 db
 // powered by miniflare/workerd and access the result from Node.js
 const productsResults = await db.prepare('SELECT * FROM products').all();
 console.log(productsResults.results);</code></pre>
            <p>With the <code>getPlatformProxy()</code> API available, the remaining work was all about updating all framework adapters, plugins, and in some cases frameworks themselves to make use of this API. We are grateful for the support we received from framework teams on this journey, especially <a href="https://github.com/alexanderniebuhr">Alex</a> from Astro, <a href="https://github.com/pi0">pi0</a> from Nuxt, <a href="https://github.com/pcattori">Pedro</a> from Remix, <a href="https://github.com/ryansolid">Ryan</a> from Solid, <a href="https://github.com/benmccann">Ben</a> and <a href="https://github.com/Rich-Harris">Rich</a> from Svelte, and our collaborator on the <a href="https://github.com/cloudflare/next-on-pages">next-on-pages</a> project, <a href="https://github.com/james-elicx">James Anderson</a>.</p>
    <div>
      <h3>Future improvements to development workflows with Vite</h3>
      <a href="#future-improvements-to-development-workflows-with-vite">
        
      </a>
    </div>
    <p>While the <a href="https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy"><code>getPlatformProxy()</code></a> API is a good solution for many scenarios, we can do better. If we could run the entire application in our JS runtime rather than Node.js, we could even more faithfully simulate the production environment and reduce developer friction and production surprises.</p><p>In the ideal world, we’d like you to develop against the same runtime that you deploy to in production, and this can only be achieved by integrating workerd directly into the dev servers of all frameworks, which is not a small feat considering the number of frameworks out there and the differences between them.</p><p>We however got a bit lucky. As we kicked off this effort, we quickly realized that <a href="https://vitejs.dev/">Vite</a>, a popular dev server used by many full-stack frameworks, was gaining increasingly greater adoption. In fact, Remix switched over to <a href="https://remix.run/blog/remix-vite-stable">Vite just recently</a> and confirmed the popularity of Vite as the common foundation for web development today.</p><p>If Vite had first-class support for running a full-stack application in an alternative JavaScript runtime, we could enable anyone using Vite to develop their applications locally with complete access to the Cloudflare developer platform. No more framework specific custom integrations and workarounds — all the features of a full-stack framework, Vite, and Cloudflare accessible to all developers.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3hj4Tb9Ex2NnwrnqZ5z6M2/e049e4300b188381259f60bc69ad054c/pasted-image-0--7-.png" />
            
            </figure><p>Sounds too good to be true? Maybe. We are very stoked to be working with the Vite team on the <a href="https://github.com/vitejs/vite/pull/16089">Vite environments</a> proposal, which could enable just that. This proposal is still evolving, so stay tuned for updates.</p>
    <div>
      <h3>What will you build today?</h3>
      <a href="#what-will-you-build-today">
        
      </a>
    </div>
    <p>We aim to make Cloudflare the best development platform for web developers. Making it quick and easy to develop your application with frameworks and tools you are already familiar with is a big part of our story. Start your journey with us by running a single command:</p><p><code>$ npm create cloudflare@latest</code></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Full Stack]]></category>
            <category><![CDATA[Wrangler]]></category>
            <category><![CDATA[Miniflare]]></category>
            <guid isPermaLink="false">14PqvEOUknz9TI2FQGbZT3</guid>
            <dc:creator>Igor Minar</dc:creator>
            <dc:creator>Dario Piotrowicz</dc:creator>
            <dc:creator>James Culveyhouse</dc:creator>
            <dc:creator>Peter Bacon Darwin</dc:creator>
        </item>
        <item>
            <title><![CDATA[Incremental adoption of micro-frontends with Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/fragment-piercing/</link>
            <pubDate>Thu, 17 Nov 2022 14:00:00 GMT</pubDate>
            <description><![CDATA[ With Cloudflare Workers, our fragment-based micro-frontend architecture, and fragment piercing technique, engineering teams can incrementally improve large frontends in a fraction of the time, yielding significant user and developer experience gains. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1Q4iw5pvW4frJry6aDHbW1/72ed68595be7d127566b976c0a4114a6/image5-11.png" />
            
            </figure>
    <div>
      <h2>Bring micro-frontend benefits to legacy Web applications</h2>
      <a href="#bring-micro-frontend-benefits-to-legacy-web-applications">
        
      </a>
    </div>
    <p>Recently, we wrote about <a href="/better-micro-frontends/">a new fragment architecture</a> for building Web applications that is fast, cost-effective, and scales to the largest projects, while enabling a fast iteration cycle. The approach uses multiple collaborating Cloudflare Workers to render and stream micro-frontends into an application that is interactive faster than traditional client-side approaches, leading to better user experience and SEO scores.</p><p>This approach is great if you are starting a new project or have the capacity to rewrite your current application from scratch. But in reality most projects are too large to be rebuilt from scratch and can adopt architectural changes only in an incremental way.</p><p>In this post we propose a way to replace only selected parts of a legacy client-side rendered application with server-side rendered fragments. The result is an application where the most important views are interactive sooner, can be developed independently, and receive all the benefits of the micro-frontend approach, while avoiding large rewrites of the legacy codebase. This approach is framework-agnostic; in this post we demonstrate fragments built with React, Qwik, and SolidJS.</p>
    <div>
      <h2>The pain of large frontend applications</h2>
      <a href="#the-pain-of-large-frontend-applications">
        
      </a>
    </div>
    <p>Many large frontend applications developed today fail to deliver good user experience. This is often caused by architectures that require large amounts of JavaScript to be downloaded, parsed and executed before users can interact with the application. Despite efforts to defer non-critical JavaScript code via lazy loading, and the use of server-side rendering, these large applications still take too long to become interactive and respond to the user's inputs.</p><p>Furthermore, large monolithic applications can be complex to build and deploy. Multiple teams may be collaborating on a single codebase and the effort to coordinate testing and deployment of the project makes it hard to develop, deploy and iterate on individual features.</p><p>As outlined in our <a href="/better-micro-frontends/">previous post</a>, micro-frontends powered by <a href="https://workers.cloudflare.com/">Cloudflare Workers</a> can solve these problems but converting an application monolith to a micro-frontend architecture can be difficult and expensive. It can take months, or even years, of engineering time before any benefits are perceived by users or developers.</p><p>What we need is an approach where a project can incrementally adopt micro-frontends into the most impactful parts of the application incrementally, without needing to rewrite the whole application in one go.</p>
    <div>
      <h2>Fragments to the rescue</h2>
      <a href="#fragments-to-the-rescue">
        
      </a>
    </div>
    <p>The goal of a fragment based architecture is to significantly <a href="https://www.cloudflare.com/solutions/ecommerce/optimization/">decrease loading and interaction latency</a> for large web applications (as measured via <a href="https://web.dev/vitals/">Core Web Vitals</a>) by breaking the application into micro-frontends that can be quickly rendered (and cached) in Cloudflare Workers. The challenge is how to integrate a micro-frontend fragment into a legacy client-side rendered application with minimal cost to the original project.</p><p>The technique we propose allows us to convert the most valuable parts of a legacy application’s UI, in isolation from the rest of the application.</p><p>It turns out that, in many applications, the most valuable parts of the UI are often nested within an application “shell” that provides header, footer, and navigational elements. Examples of these include a login form, product details panel in an <a href="https://www.cloudflare.com/ecommerce/">e-commerce application</a>, the inbox in an email client, etc.</p><p>Let’s take a login form as an example. If it takes our application several seconds to display the login form, the users will dread logging in, and we might lose them. We can however convert the login form into a server-side rendered fragment, which is displayed and interactive immediately, while the rest of the legacy application boots up in the background. Since the fragment is interactive early, the user can even submit their credentials before the legacy application has started and rendered the rest of the page.</p><div></div>
<p><small>Animation showing the login form being available before the main application</small></p><p>This approach enables engineering teams to deliver valuable improvements to users in just a fraction of the time and engineering cost compared to traditional approaches, which either sacrifice user experience improvements, or require a lengthy and high-risk rewrite of the entire application. It allows teams with monolithic single-page applications to adopt a micro-frontend architecture incrementally, target the improvements to the most valuable parts of the application, and therefore front-load the return on investment.</p><p>An interesting challenge in extracting parts of the UI into server-side rendered fragments is that, once displayed in the browser, we want the legacy application and the fragments to feel like a single application. The fragments should be neatly embedded within the legacy application shell, keeping the application accessible by correctly forming the DOM hierarchy, but we also want the server-side rendered fragments to be displayed and become interactive as quickly as possible — even before the legacy client-side rendered application shell comes into existence. How can we embed UI fragments into an application shell that doesn’t exist yet? We resolved this problem via a technique we devised, which we call “fragment piercing”.</p>
    <div>
      <h2>Fragment piercing</h2>
      <a href="#fragment-piercing">
        
      </a>
    </div>
    <p>Fragment piercing combines HTML/DOM produced by server-side rendered micro-frontend fragments with HTML/DOM produced by a legacy client-side rendered application.</p><p>The micro-frontend fragments are rendered directly into the top level of the HTML response, and are designed to become immediately interactive. In the background, the legacy application is client-side rendered as a sibling of these fragments. When it is ready, the fragments are “pierced” into the legacy application – the DOM of each fragment is moved to its appropriate place within the DOM of the legacy application – without causing any visual side effects, or loss of client-side state, such as focus, form data, or text selection. Once “pierced”, a fragment can begin to communicate with the legacy application, effectively becoming an integrated part of it.</p><p>Here, you can see a “login” fragment and the empty legacy application “root” element at the top level of the DOM, before piercing.</p>
            <pre><code>&lt;body&gt;
  &lt;div id="root"&gt;&lt;/div&gt;
  &lt;piercing-fragment-host fragment-id="login"&gt;
    &lt;login q:container...&gt;...&lt;/login&gt;
  &lt;/piercing-fragment-host&gt;
&lt;/body&gt;</code></pre>
            <p>And here you can see that the fragment has been pierced into the “login-page” div in the rendered legacy application.</p>
            <pre><code>&lt;body&gt;
  &lt;div id="root"&gt;
    &lt;header&gt;...&lt;/header&gt;
    &lt;main&gt;
      &lt;div class="login-page"&gt;
        &lt;piercing-fragment-outlet fragment-id="login"&gt;
          &lt;piercing-fragment-host fragment-id="login"&gt;
            &lt;login  q:container...&gt;...&lt;/login&gt;
          &lt;/piercing-fragment-host&gt;
        &lt;/piercing-fragment-outlet&gt;
      &lt;/div&gt;
    &lt;/main&gt;
    &lt;footer&gt;...&lt;/footer&gt;
  &lt;/div&gt;
&lt;/body&gt;</code></pre>
            <p>To keep the fragment from moving and causing a visible layout shift during this transition, we apply CSS styles that position the fragment in the same way before and after piercing.</p><p>At any time an application can be displaying any number of pierced fragments, or none at all. This technique is not limited only to the initial load of the legacy application. Fragments can also be added to and removed from an application, at any time. This allows fragments to be rendered in response to user interactions and client-side routing.</p><p>With fragment piercing, you can start to incrementally adopt micro-frontends, one fragment at a time. You decide on the granularity of fragments, and which parts of the application to turn into fragments. The fragments don’t all have to use the same Web framework, which can be useful when switching stacks, or during a post-acquisition integration of multiple applications.</p>
    <div>
      <h2>The “Productivity Suite” demo</h2>
      <a href="#the-productivity-suite-demo">
        
      </a>
    </div>
    <p>As a demonstration of fragment piercing and incremental adoption we have developed a <a href="https://github.com/cloudflare/workers-web-experiments/tree/main/productivity-suite">“productivity suite” demo</a> application that allows users to manage to-do lists, read hacker news, etc. We implemented the shell of this application as a client-side rendered React application — a common tech choice in corporate applications. This is our “legacy application”. There are three routes in the application that have been updated to use micro-frontend fragments:</p><ul><li><p><code>/login</code> - a simple dummy login form with client-side validation, displayed when users are not authenticated (implemented in <a href="https://qwik.builder.io/">Qwik</a>).</p></li><li><p><code>/todos</code> - manages one or more todo lists, implemented as two collaborating fragments:</p><ul><li><p>Todo list selector - a component for selecting/creating/deleting Todo lists (implemented in <a href="https://qwik.builder.io/">Qwik</a>).</p></li><li><p>Todo list editor - a clone of the <a href="https://todomvc.com/">TodoMVC</a> app (implemented in <a href="https://reactjs.org/docs/react-dom-server.html">React</a>).</p></li></ul></li><li><p><code>/news</code> - a clone of the <a href="https://github.com/solidjs/solid-hackernews">HackerNews</a> demo (implemented in <a href="https://www.solidjs.com/">SolidJS</a>).</p></li></ul><p>This demo showcases that different independent technologies can be used for both the legacy application and for each of the fragments.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/38jTYqRteZyGozPUoqXd8D/60b003aa2b53395b4adcb0cf31dcd2fc/image2-41.png" />
            
            </figure><p>A visualization of the fragments that are pierced into the legacy application</p><p>The application is deployed at <a href="https://productivity-suite.web-experiments.workers.dev/">https://productivity-suite.web-experiments.workers.dev/</a>.</p><p>To try it out, you first need to log in – simply use any username you like (no password needed). The user’s data is saved in a cookie, so you can log out and back in using the same username. After you’ve logged in, navigate through the various pages using the navigation bar at the top of the application. In particular, take a look at the “<a href="https://productivity-suite.web-experiments.workers.dev/todos">Todo Lists</a>” and “<a href="https://productivity-suite.web-experiments.workers.dev/news">News</a>” pages to see the piercing in action.</p><p>At any point, try reloading the page to see that fragments are rendered instantly while the legacy application loads slowly in the background. Try interacting with the fragments even before the legacy application has appeared!</p><p>At the very top of the page there are controls to let you see the impact of fragment piercing in action.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/11Y8DDksAEPz1AMQT8jjSG/8c69e3cc1d99b8a67a01410b146f2c02/image1-56.png" />
            
            </figure><ul><li><p>Use the “Legacy app bootstrap delay” slider to set the simulated delay before the legacy application starts.</p></li><li><p>Toggle “Piercing Enabled” to see what the user experience would be if the app did not use fragments.</p></li><li><p>Toggle “Show Seams” to see where each fragment is on the current page.</p></li></ul>
    <div>
      <h2>How it works</h2>
      <a href="#how-it-works">
        
      </a>
    </div>
    <p>The application is composed of a number of building blocks.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/47B8E1C6o3kWhsEzUWYL7J/ca1e6348128985d560bd28f0dc32615f/Frame-653.png" />
            
            </figure><p>An overview of the collaborating Workers and legacy application host</p><p>The <b>Legacy application host</b> in our demo serves the files that define the client-side React application (HTML, JavaScript and stylesheets). Applications built with other tech stacks would work just as well. The <b>Fragment Workers</b> host the micro-frontend fragments, as described in our previous <a href="/better-micro-frontends/">fragment architecture</a> post. And the <b>Gateway Worker</b> handles requests from the browser, selecting, fetching and combining response streams from the legacy application and micro-frontend fragments.</p><p>Once these pieces are all deployed, they work together to handle each request from the browser. Let’s look at what happens when you go to the `/login` route.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2atnhkVHu0tNa1MKGWnOPs/b80838e713fd7b64a177a82b06377a16/image4-22.png" />
            
            </figure><p>The flow of requests when viewing the login page</p><p>The user navigates to the application and the browser makes a request to the Gateway Worker to get the initial HTML. The Gateway Worker identifies that the browser is requesting the login page. It then makes two parallel sub-requests – one to fetch the index.html of the legacy application, and another to request the server-side rendered login fragment. It then combines these two responses into a single response stream containing the HTML that is delivered to the browser.</p><p>The browser displays the HTML response containing the empty root element for the legacy application, and the server-side rendered login fragment, which is immediately interactive for the user.</p><p>The browser then requests the legacy application’s JavaScript. This request is proxied by the Gateway Worker to the Legacy application host. Similarly, any other assets for the legacy application or fragments get routed through the Gateway Worker to the legacy application host or appropriate Fragment Worker.</p><p>Once the legacy application’s JavaScript has been downloaded and executed, rendering the shell of the application in the process, the fragment piercing kicks in, moving the fragment into the appropriate place in the legacy application, while preserving all of its UI state.</p><p>While focussed on the login fragment to explain fragment piercing, the same ideas apply to the other fragments implemented in the <code>/todos</code> and <code>/news</code> routes.</p>
    <div>
      <h2>The piercing library</h2>
      <a href="#the-piercing-library">
        
      </a>
    </div>
    <p>Despite being implemented using different Web frameworks, all the fragments are integrated into the legacy application in the same way using helpers from a “<a href="https://github.com/cloudflare/workers-web-experiments/tree/main/productivity-suite/piercing-library">Piercing Library</a>”. This library is a collection of server-side and client-side utilities that we developed, for the demo, to handle integrating the legacy application with micro-frontend fragments. The main features of the library are the <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/piercing-gateway.ts#L82"><code>PiercingGateway</code></a> class, <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/piercing-fragment-host/piercing-fragment-host.ts#L5">fragment host</a> and <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/piercing-fragment-outlet.ts#L31">fragment outlet</a> custom elements, and the <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/message-bus/message-bus.ts#L18"><code>MessageBus</code></a> class.</p>
    <div>
      <h3>PiercingGateway</h3>
      <a href="#piercinggateway">
        
      </a>
    </div>
    <p>The <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/piercing-gateway.ts#L82"><code>PiercingGateway</code></a> class can be used to instantiate a Gateway Worker that handles all requests for our application’s HTML, JavaScript and other assets. The `PiercingGateway` routes requests through to the appropriate Fragment Workers or to the host of the Legacy Application. It also combines the HTML response streams from these fragments with the response from the legacy application into a single HTML stream that is returned to the browser.</p><p>Implementing a Gateway Worker is straightforward using the Piercing Library. Create a new <code>gateway</code> instance of <code>PiercingGateway</code>, passing it the URL to the legacy application host and a function to determine whether piercing is enabled for the given request. Export the <code>gateway</code> as the default export from the Worker script so that the Workers runtime can wire up its <code>fetch()</code> handler.</p>
            <pre><code>const gateway = new PiercingGateway&lt;Env&gt;({
  // Configure the origin URL for the legacy application.
  getLegacyAppBaseUrl: (env) =&gt; env.APP_BASE_URL,
  shouldPiercingBeEnabled: (request) =&gt; ...,
});
...

export default gateway;</code></pre>
            <p>Fragments can be registered by calling the <code>registerFragment()</code> method so that the <code>gateway</code> can automatically route requests for a fragment’s HTML and assets to its Fragment Worker. For example, registering the login fragment would look like:</p>
            <pre><code>gateway.registerFragment({
  fragmentId: "login",
  prePiercingStyles: "...",
  shouldBeIncluded: async (request) =&gt; !(await isUserAuthenticated(request)),
});</code></pre>
            
    <div>
      <h3>Fragment host and outlet</h3>
      <a href="#fragment-host-and-outlet">
        
      </a>
    </div>
    <p>Routing requests and combining HTML responses in the Gateway Worker is only half of what makes piercing possible. The other half needs to happen in the browser where the fragments need to be pierced into the legacy application using the technique we described earlier.</p><p>The fragment piercing in the browser is facilitated by a pair of <a href="https://html.spec.whatwg.org/multipage/custom-elements.html">custom elements</a>, the fragment host (<a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/piercing-fragment-host/piercing-fragment-host.ts#L5"><code>&lt;piercing-fragment-host&gt;</code></a>) and the fragment outlet (<a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/piercing-fragment-outlet.ts#L31"><code>&lt;piercing-fragment-outlet&gt;</code></a>).</p><p>The Gateway Worker wraps the HTML for each fragment in a fragment host. In the browser, the fragment host manages the life-time of the fragment and is used when moving the fragment’s DOM into position in the legacy application.</p>
            <pre><code>&lt;piercing-fragment-host fragment-id="login"&gt;
  &lt;login q:container...&gt;...&lt;/login&gt;
&lt;/piercing-fragment-host&gt;</code></pre>
            <p>In the legacy application, the developer marks where a fragment should appear when it is pierced by adding a fragment outlet. Our demo application’s Login route looks as follows:</p>
            <pre><code>export function Login() {
  …
  return (
    &lt;div className="login-page" ref={ref}&gt;
      &lt;piercing-fragment-outlet fragment-id="login" /&gt;
    &lt;/div&gt;
  );
}</code></pre>
            <p>When a fragment outlet is added to the DOM, it searches the current document for its associated fragment host. If found, the fragment host and its contents are moved inside the outlet. If the fragment host is not found, the outlet will make a request to the gateway worker to fetch the fragment HTML, which is then streamed directly into the fragment outlet, using the <a href="https://github.com/marko-js/writable-dom">writable-dom library</a> (a small but powerful library developed by the <a href="https://markojs.com/">MarkoJS</a> team).</p><p>This fallback mechanism enables client-side navigation to routes that contain new fragments. This way fragments can be rendered in the browser via both initial (hard) navigation and client-side (soft) navigation.</p>
    <div>
      <h3>Message bus</h3>
      <a href="#message-bus">
        
      </a>
    </div>
    <p>Unless the fragments in our application are completely presentational or self-contained, they also need to communicate with the legacy application and other fragments. The <code>[MessageBus](https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/message-bus/message-bus.ts#L18)</code> is a simple asynchronous, isomorphic, and framework-agnostic communication bus that the legacy application and each of the fragments can access.</p><p>In our demo application the login fragment needs to inform the legacy application when the user has authenticated. This <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/app/fragments/login/src/components/LoginForm.tsx#L51-L57">message dispatch</a> is implemented in the Qwik <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/app/fragments/login/src/components/LoginForm.tsx#L38"><code>LoginForm</code></a> component as follows:</p>
            <pre><code>const dispatchLoginEvent = $(() =&gt; {
  getBus(ref.value).dispatch("login", {
    username: state.username,
    password: state.password,
  });
  state.loading = true;
});</code></pre>
            <p>The legacy application can then <a href="https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/app/legacy-app/src/auth.tsx#L24-L34">listen for these messages</a> like this:</p>
            <pre><code>useEffect(() =&gt; {
  return getBus().listen&lt;LoginMessage&gt;("login", async (user) =&gt; {
    setUser(user);
    await addUserDataIfMissing(user.username);
    await saveCurrentUser(user.username);
    getBus().dispatch("authentication", user);
    navigate("/", { replace: true, });
  });
}, []);</code></pre>
            <p>We settled on this message bus implementation because we needed a solution that was framework-agnostic, and worked well on both the server as well as client.</p>
    <div>
      <h2>Give it a go!</h2>
      <a href="#give-it-a-go">
        
      </a>
    </div>
    <p>With fragments, fragment piercing, and Cloudflare Workers, you can improve performance as well as the development cycle of legacy client-side rendered applications. These changes can be adopted incrementally, and you can even do so while implementing fragments with a Web framework for your choice.</p><p>The “Productivity Suite” application demonstrating these capabilities can be found at <a href="https://productivity-suite.web-experiments.workers.dev/">https://productivity-suite.web-experiments.workers.dev/</a>.</p><p>All the code we have shown is open-source and published to Github: <a href="https://github.com/cloudflare/workers-web-experiments/tree/main/productivity-suite">https://github.com/cloudflare/workers-web-experiments/tree/main/productivity-suite</a>.</p><p>Feel free to clone the repository. It is easy to run locally and even deploy your own version (for free) to Cloudflare. We tried to make the code as reusable as possible. Most of the core logic is in the <a href="https://github.com/cloudflare/workers-web-experiments/tree/main/productivity-suite/piercing-library">piercing library</a> that you could try in your own projects. We’d be thrilled to receive feedback, suggestions, or hear about applications you’d like to use it for. Join our <a href="https://github.com/cloudflare/workers-web-experiments/discussions/64">GitHub discussion</a> or also reach us on our <a href="https://discord.com/channels/595317990191398933/1041751020340002907">discord channel</a>.</p><p>We believe that combining Cloudflare Workers with the latest ideas from frameworks will drive the next big steps forward in improved experiences for both users and developers in Web applications. Expect to see more demos, blog posts and collaborations as we continue to push the boundaries of what the Web can offer. And if you’d also like to be directly part of this journey, we are also happy to share that <a href="https://boards.greenhouse.io/cloudflare/jobs/4619341">we are hiring</a>!</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Edge]]></category>
            <category><![CDATA[Micro-frontends]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">4vnFkyih2W2DD0QcaALcdf</guid>
            <dc:creator>Peter Bacon Darwin</dc:creator>
            <dc:creator>Dario Piotrowicz</dc:creator>
            <dc:creator>James Culveyhouse</dc:creator>
            <dc:creator>Igor Minar</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare Workers and micro-frontends: made for one another]]></title>
            <link>https://blog.cloudflare.com/better-micro-frontends/</link>
            <pubDate>Thu, 20 Oct 2022 13:00:00 GMT</pubDate>
            <description><![CDATA[ In this blog-post we demonstrate how hosting and combining multiple server-side rendered micro-frontends on Cloudflare Workers offer a highly scalable, high performance solution to these problems ]]></description>
            <content:encoded><![CDATA[ <p>To help developers build better web applications we researched and devised a fragments architecture to build <a href="https://martinfowler.com/articles/micro-frontends.html">micro-frontends</a> using <a href="https://workers.cloudflare.com/">Cloudflare Workers</a> that is lightning fast, cost-effective to develop and operate, and scales to the needs of the largest enterprise teams without compromising release velocity or user experience.</p><p>Here we share a technical overview and a proof of concept of this architecture.</p>
    <div>
      <h2>Why micro-frontends?</h2>
      <a href="#why-micro-frontends">
        
      </a>
    </div>
    <p>One of the challenges of modern frontend web development is that applications are getting bigger and more complex. This is especially true for enterprise web applications supporting <a href="https://www.cloudflare.com/ecommerce/">e-commerce</a>, banking, insurance, travel, and other industries, where a unified user interface provides access to a large amount of functionality. In such projects it is common for many teams to collaborate to build a single web application. These monolithic web applications, usually built with JavaScript technologies like React, Angular, or Vue, span thousands, or even millions of lines of code.</p><p>When a monolithic JavaScript architecture is used with applications of this scale, the result is a slow and fragile user experience with low <a href="https://web.dev/measure/">Lighthouse scores</a>. Furthermore, collaborating development teams often struggle to <a href="https://www.youtube.com/watch?v=pU1gXA0rfwc">maintain and evolve</a> their parts of the application, as their <a href="https://igor.dev/posts/fate-sharing-and-micro-frontends/">fates are tied</a> with fates of all the other teams, so the mistakes and tech debt of one team often impacts all.</p><p>Drawing on ideas from <a href="https://en.wikipedia.org/wiki/Microservices">microservices</a>, the frontend community has started to advocate for <a href="https://micro-frontends.org/">micro-frontends</a> to enable teams to develop and deploy their features independently of other teams. Each micro-frontend is a self-contained mini-application, that can be developed and released independently, and is responsible for rendering a “fragment” of the page. The application then combines these fragments together so that from the user's perspective it feels like a single application.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/78VTFfttaimJC7Son9VcYZ/652e6f47dd6354864fd99102307659ba/image3.jpg" />
            
            </figure><p>An application consisting of multiple micro-frontends</p><p>Fragments could represent vertical application features, like “account management” or “checkout”, or horizontal features, like “header” or “navigation bar”.</p>
    <div>
      <h3>Client-side micro-frontends</h3>
      <a href="#client-side-micro-frontends">
        
      </a>
    </div>
    <p>A common approach to micro-frontends is to rely upon client-side code to lazy load and stitch fragments together (e.g. via <a href="https://webpack.js.org/concepts/module-federation/">Module Federation</a>). Client-side micro-frontend applications suffer from a number of problems.</p><p>Common code must either be duplicated or published as a shared library. Shared libraries are problematic themselves. It is not possible to <a href="https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking">tree-shake</a> unused library code at build time resulting in more code than necessary being downloaded to the browser and coordinating between teams when shared libraries need to be updated can be complex and awkward.</p><p>Also, the top-level container application must bootstrap before the micro-frontends can even be requested, and they also need to boot before they become interactive. If they are nested, then you may end up getting a <a href="https://javascript.plainenglish.io/react-official-answer-the-right-way-of-requesting-data-in-react18-other-frameworks-also-apply-50d907c1f6c4">waterfall of requests</a> to get micro-frontends leading to further runtime delays.</p><p>These problems can result in a sluggish application startup experience for the user.</p><p>Server-side rendering could be used with client-side micro-frontends to improve how quickly a browser displays the application but implementing this can significantly increase the complexity of development, deployment and operation. Furthermore, most server-side rendering approaches still suffer from a <a href="https://dev.to/this-is-learning/why-efficient-hydration-in-javascript-frameworks-is-so-challenging-1ca3">hydration delay</a> before the user can fully interact with the application.</p><p>Addressing these challenges was the main motivation for exploring an alternative solution, which relies on the distributed, low latency properties provided by Cloudflare Workers.</p>
    <div>
      <h2>Micro-frontends on Cloudflare Workers</h2>
      <a href="#micro-frontends-on-cloudflare-workers">
        
      </a>
    </div>
    <p><a href="https://workers.cloudflare.com/">Cloudflare Workers</a> is a compute platform that offers a highly scalable, low latency JavaScript execution environment that is available in <a href="https://www.cloudflare.com/network/">over 275 locations</a> around the globe. In our exploration we used Cloudflare Workers to host and render micro-frontends from anywhere on our global network.</p>
    <div>
      <h3>Fragments architecture</h3>
      <a href="#fragments-architecture">
        
      </a>
    </div>
    <p>In this architecture the application consists of a tree of “fragments” each deployed to Cloudflare Workers that collaborate to server-side render the overall response. The browser makes a request to a “root fragment”, which will communicate with “child fragments” to generate the final response. Since Cloudflare Workers can communicate with each other <a href="https://developers.cloudflare.com/workers/platform/bindings/about-service-bindings/">with almost no overhead</a>, applications can be server-side rendered quickly by child fragments, all working in parallel to render their own HTML, streaming their results to the parent fragment, which combines them into the final response stream delivered to the browser.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/43AlwyalVCD1CfOm31lNiX/f740b01c554b0f574267cf8c4dfe245c/blog-1447.png" />
            
            </figure><p>A high-level overview of a fragments architecture</p>
    <div>
      <h2>Visit the “Cloud Gallery”</h2>
      <a href="#visit-the-cloud-gallery">
        
      </a>
    </div>
    <p>We have built an example of a “Cloud Gallery” application to show how this can work in practice. It is deployed to Cloudflare Workers at  <a href="https://cloud-gallery.web-experiments.workers.dev/">https://cloud-gallery.web-experiments.workers.dev/</a></p><p>The demo application is a simple filtered gallery of cloud images built using our fragments architecture. Try selecting a tag in the type-ahead to filter the images listed in the gallery. Then change the delay on the stream of cloud images to see how the type-ahead filtering can be interactive before the page finishes loading.</p>
    <div>
      <h3>Multiple Cloudflare Workers</h3>
      <a href="#multiple-cloudflare-workers">
        
      </a>
    </div>
    <p>The application is composed of a tree of six collaborating but independently deployable Cloudflare Workers, each rendering their own fragment of the screen and providing their own client-side logic, and assets such as CSS stylesheets and images.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2xfKbBmdC16pAkNbdCjOz7/0a3e7eebadd5272421fe10dbbb594d76/image6-4.png" />
            
            </figure><p>Architectural overview of the Cloud Gallery app</p><p>The “main” fragment acts as the root of the application. The “header” fragment has a slider to configure an artificial delay to the display of gallery images. The “body” fragment contains the “filter” fragment and “gallery” fragments. Finally, the “footer” fragment just shows some static content.</p><p>The full source code of the demo app is available on <a href="https://github.com/cloudflare/workers-web-experiments/tree/main/cloud-gallery">GitHub</a>.</p>
    <div>
      <h2>Benefits and features</h2>
      <a href="#benefits-and-features">
        
      </a>
    </div>
    <p>This architecture of multiple collaborating server-side rendered fragments, deployed to Cloudflare Workers has some interesting features.</p>
    <div>
      <h3>Encapsulation</h3>
      <a href="#encapsulation">
        
      </a>
    </div>
    <p>Fragments are entirely encapsulated, so they can control what they own and what they make available to other fragments.</p>
    <div>
      <h4><i>Fragments can be developed and deployed independently</i></h4>
      <a href="#fragments-can-be-developed-and-deployed-independently">
        
      </a>
    </div>
    <p>Updating one of the fragments is as simple as redeploying that fragment. The next request to the main application will use the new fragment. Also, fragments can host their own assets (client-side JavaScript, images, etc.), which are streamed through their parent fragment to the browser.</p>
    <div>
      <h4><i>Server-only code is not sent to the browser</i></h4>
      <a href="#server-only-code-is-not-sent-to-the-browser">
        
      </a>
    </div>
    <p>As well as reducing the cost of downloading unnecessary code to the browser, security sensitive code that is only needed for server-side rendering the fragment is never exposed to other fragments and is not downloaded to the browser. Also, features can be safely hidden behind feature flags in a fragment, allowing more flexibility with rolling out new behavior safely.</p>
    <div>
      <h3>Composability</h3>
      <a href="#composability">
        
      </a>
    </div>
    <p>Fragments are fully composable - any fragment can contain other fragments. The resulting tree structure gives you more flexibility in how you architect and deploy your application. This helps larger projects to scale their development and deployment. Also, fine-grain control over how fragments are composed, could allow fragments that are expensive to server-side render to be cached individually.</p>
    <div>
      <h3>Fantastic Lighthouse scores</h3>
      <a href="#fantastic-lighthouse-scores">
        
      </a>
    </div>
    <p>Streaming server-rendered HTML results in great user experiences and <a href="https://developer.chrome.com/docs/lighthouse/overview/">Lighthouse</a> scores, which in practice means happier users and higher chance of conversions for your business.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4GNNIIhE13dbX7EwfTUoQ8/47168493eec000d1df7eb71db56bf271/image7-2.png" />
            
            </figure><p>Lighthouse scores for the Cloud Gallery app</p><p>Each fragment can parallelize requests to its child fragments and pipe the resulting HTML streams into its own single streamed server-side rendered response. Not only can this reduce the time to render the whole page but streaming each fragment through to the browser reduces the time to the first byte of each fragment.</p>
    <div>
      <h3>Eager interactivity</h3>
      <a href="#eager-interactivity">
        
      </a>
    </div>
    <p>One of the powers of a fragments architecture is that fragments can become interactive even while the rest of the application (including other fragments) is still being streamed down to the browser.</p><p>In our demo, the “filter” fragment is immediately interactive as soon as it is rendered, even if the image HTML for the “gallery” fragment is still loading.</p><p>To make it easier to see this, we added a slider to the top of the “header” that can simulate a network or database delay that slows down the HTML stream which renders the “gallery” images. Even when the “gallery” fragment is still loading, the type-ahead input, in the “filter” fragment, is already fully interactive.</p><p>Just think of all the frustration that this eager interactivity could avoid for web application users with unreliable Internet connection.</p>
    <div>
      <h2>Under the hood</h2>
      <a href="#under-the-hood">
        
      </a>
    </div>
    <p>As discussed already this architecture relies upon deploying this application as many cooperating Cloudflare Workers. Let’s look into some details of how this works in practice.</p><p>We experimented with various technologies, and while this approach can be used with many frontend libraries and frameworks, we found the <a href="https://qwik.builder.io/">Qwik framework</a> to be a particularly good fit, because of its HTML-first focus and low JavaScript overhead, which avoids any hydration problems.</p>
    <div>
      <h3>Implementing a fragment</h3>
      <a href="#implementing-a-fragment">
        
      </a>
    </div>
    <p>Each fragment is a server-side rendered Qwik application deployed to its own Cloudflare Worker. This means that you can even browse to these fragments directly. For example, the “header” fragment is deployed to <a href="https://cloud-gallery-header.web-experiments.workers.dev/">https://cloud-gallery-header.web-experiments.workers.dev/</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/24SSUszIf2fbUphL5lsfr9/756d04fc6bc455f3845b45bf1ff592c0/image8-2.png" />
            
            </figure><p>A screenshot of the self-hosted “header” fragment</p><p>The header fragment is defined as a <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/header/src/root.tsx"><code>Header</code></a> component using Qwik. This component is rendered in a Cloudflare Worker via a <code>fetch()</code> handler:</p>
            <pre><code>export default {
  fetch(request: Request, env: Record&lt;string, unknown&gt;): Promise&lt;Response&gt; {
    return renderResponse(request, env, &lt;Header /&gt;, manifest, "header");
  },
};</code></pre>
            <small><a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/header/src/entry.ssr.tsx"><b>cloud-gallery/header/src/entry.ssr.tsx</b></a></small><br /><p>The <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/renderResponse.ts"><code><u>renderResponse</u></code></a><code>()</code> function is a helper we wrote that server-side renders the fragment and streams it into the body of a <code>Response</code> that we return from the <code>fetch()</code> handler.</p><p>The header fragment serves its own JavaScript and image assets from its Cloudflare Worker. We configure <a href="https://developers.cloudflare.com/workers/wrangler"><u>Wrangler</u></a> to upload these assets to Cloudflare and serve them from our network.</p>
    <div>
      <h2>Implementing fragment composition</h2>
      <a href="#implementing-fragment-composition">
        
      </a>
    </div>
    <p>Fragments that contain child fragments have additional responsibilities:</p><ul><li><p>Request and inject child fragments when rendering their own HTML.</p></li><li><p>Proxy requests for child fragment assets through to the appropriate fragment.</p></li></ul>
    <div>
      <h3>Injecting child fragments</h3>
      <a href="#injecting-child-fragments">
        
      </a>
    </div>
    <p>The position of a child fragment inside its parent can be specified by a <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/fragmentHelpers.tsx"><code>FragmentPlaceholder</code></a> helper component that we have developed. For example, the “body” fragment has the “filter” and “gallery” fragments.</p>
            <pre><code>&lt;div class="content"&gt;
  &lt;FragmentPlaceholder name="filter" /&gt;
  &lt;FragmentPlaceholder name="gallery" /&gt;
&lt;/div&gt;</code></pre>
            <small><a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/body/src/root.tsx"><b>cloud-gallery/body/src/root.tsx</b></a></small><br /><p>The <code>FragmentPlaceholder</code> component is responsible for making a request for the fragment and piping the fragment stream into the output stream.</p>
    <div>
      <h3>Proxying asset requests</h3>
      <a href="#proxying-asset-requests">
        
      </a>
    </div>
    <p>As mentioned earlier, fragments can host their own assets, especially client-side JavaScript files. When a request for an asset arrives at the parent fragment, it needs to know which child fragment should receive the request.</p><p>In our demo we use a convention that such asset paths will be prefixed with <code>/_fragment/&lt;fragment-name&gt;</code>. For example, the header logo image path is <code>/_fragment/header/cf-logo.png</code>. We developed a <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/fragmentHelpers.tsx#L28"><code><u>tryGetFragmentAsset</u></code></a><code>()</code> helper which can be added to the parent fragment’s <code>fetch()</code> handler to deal with this:</p>
            <pre><code>export default {
  async fetch(
    request: Request,
    env: Record&lt;string, unknown&gt;
  ): Promise&lt;Response&gt; {
    // Proxy requests for assets hosted by a fragment.
    const asset = await tryGetFragmentAsset(env, request);
    if (asset !== null) {
      return asset;
    }
    // Otherwise server-side render the template injecting child fragments.
    return renderResponse(request, env, &lt;Root /&gt;, manifest, "div");
  },
};</code></pre>
            <small><a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/body/src/entry.ssr.tsx"><strong>cloud-gallery/body/src/entry.ssr.tsx</strong></a></small><br />
    <div>
      <h3>Fragment asset paths</h3>
      <a href="#fragment-asset-paths">
        
      </a>
    </div>
    <p>If a fragment hosts its own assets, then we need to ensure that any HTML it renders uses the special <code>_fragment/&lt;fragment-name&gt;</code> path prefix mentioned above when referring to these assets. We have implemented a strategy for this in the helpers we developed.</p><p>The <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/fragmentHelpers.tsx"><code><u>FragmentPlaceholder</u></code></a> component adds a `base` searchParam to the fragment request to tell it what this prefix should be. The <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/renderResponse.ts"><code><u>renderResponse</u></code></a><code>()</code> helper extracts this prefix and provides it to the Qwik server-side renderer. This ensures that any request for client-side JavaScript has the correct prefix. Fragments can apply a hook that we developed called <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/base.ts#L22"><code><u>useFragmentRoot</u></code></a><code>()</code>. This allows components to gather the prefix from a <a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/base.ts#L12"><code><u>FragmentContext</u></code></a> <a href="https://qwik.builder.io/docs/components/context/"><u>context</u></a>.</p><p>For example, since the “header” fragment hosts the Cloudflare and Github logos as assets, it must call the <code>useFragmentRoot()</code> hook:</p>
            <pre><code>export const Header = component$(() =&gt; {
  useStylesScoped$(HeaderCSS);
  useFragmentRoot();

  return (...);
});</code></pre>
            <small><a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/header/src/root.tsx"><strong>cloud-gallery/header/src/root.tsx</strong></a></small><p>The <code>FragmentContext</code> value can then be accessed in components that need to apply the prefix. For example, the <code>Image</code> component:</p>
            <pre><code>export const Image = component$((props: Record&lt;string, string | number&gt;) =&gt; {
  const { base } = useContext(FragmentContext);
  return &lt;img {...props} src={base + props.src} /&gt;;
});</code></pre>
            <small><a href="https://github.com/cloudflare/workers-web-experiments/blob/main/cloud-gallery/helpers/src/image/image.tsx"><strong>cloud-gallery/helpers/src/image/image.tsx</strong></a></small>
    <div>
      <h3>Service-binding fragments</h3>
      <a href="#service-binding-fragments">
        
      </a>
    </div>
    <p>Cloudflare Workers provide a mechanism called <a href="https://developers.cloudflare.com/workers/platform/bindings/about-service-bindings/">service bindings</a> to make requests between Cloudflare Workers efficiently that avoids network requests. In the demo we use this mechanism to make the requests from parent fragments to their child fragments with almost no performance overhead, while still allowing the fragments to be independently deployed.</p>
    <div>
      <h2>Comparison to current solutions</h2>
      <a href="#comparison-to-current-solutions">
        
      </a>
    </div>
    <p>This fragments architecture has three properties that distinguish it from other current solutions.</p><p>Unlike monoliths, or client-side micro-frontends, fragments are developed and deployed as independent server-side rendered applications that are composed together on the server-side. This significantly improves rendering speed, and lowers interaction latency in the browser.</p><p>Unlike server-side rendered micro-frontends with Node.js or cloud functions, Cloudflare Workers is a globally distributed compute platform with a region-less deployment model. It has incredibly low latency, and a near-zero communication overhead between fragments.</p><p>Unlike solutions based on <a href="https://webpack.js.org/concepts/module-federation/">module federation</a>, a fragment's client-side JavaScript is very specific to the fragment it is supporting. This means that it is small enough that we don’t need to have shared library code, eliminating the version skew issues and coordination problems when updating shared libraries.</p>
    <div>
      <h2>Future possibilities</h2>
      <a href="#future-possibilities">
        
      </a>
    </div>
    <p>This demo is just a proof of concept, so there are still areas to investigate. Here are some of the features we’d like to explore in the future.</p>
    <div>
      <h3>Caching</h3>
      <a href="#caching">
        
      </a>
    </div>
    <p>Each micro-frontend fragment can be cached independently of the others based on how static its content is. When requesting the full page, the fragments only need to run server-side rendering for micro-frontends that have changed.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3PxpkF17fG0fMqBX0nhT6n/1789385a464d8908f7646a1570c6314d/image2.jpg" />
            
            </figure><p>An application where the output of some fragments are cached</p><p>With per-fragment caching you can return the HTML response to the browser faster, and avoid incurring compute costs in re-rendering content unnecessarily.</p>
    <div>
      <h3>Fragment routing and client-side navigation</h3>
      <a href="#fragment-routing-and-client-side-navigation">
        
      </a>
    </div>
    <p>Our demo application used micro-frontend fragments to compose a single page. We could however use this approach to implement page routing as well. When server-side rendering, the main fragment could insert the appropriate “page” fragment based on the visited URL. When navigating, client-side, within the app, the main fragment would remain the same while the displayed “page” fragment would change.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/31uGNgOqJccEZXHRfrK7Fw/a69dd9152b184802d07c437347560251/image5-1.jpg" />
            
            </figure><p>An application where each route is delegated to a different fragment</p><p>This approach combines the best of server-side and client-side routing with the power of fragments.</p>
    <div>
      <h3>Using other frontend frameworks</h3>
      <a href="#using-other-frontend-frameworks">
        
      </a>
    </div>
    <p>Although the Cloud Gallery application uses Qwik to implement all fragments, it is possible to use other frameworks as well. If really necessary, it’s even possible to mix and match frameworks.</p><p>To achieve good results, the framework of choice should be capable of server-side rendering, and should have a small client-side JavaScript footprint. HTML streaming capabilities, while not required, can significantly improve performance of large applications.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1AkgbhioqHBaKtvZxVTIsp/6a818eeb06c4ad842886e81f9d0f477a/image1-15.png" />
            
            </figure><p>An application using different frontend frameworks</p>
    <div>
      <h3>Incremental migration strategies</h3>
      <a href="#incremental-migration-strategies">
        
      </a>
    </div>
    <p>Adopting a new architecture, compute platform, and deployment model is a lot to take in all at once, and for existing large applications is prohibitively risky and expensive. To make this  fragment-based architecture available to legacy projects, an incremental adoption strategy is a key.</p><p>Developers could test the waters by migrating just a single piece of the user-interface within their legacy application to a fragment, integrating with minimal changes to the legacy application. Over time, more of the application could then be moved over, one fragment at a time.</p>
    <div>
      <h3>Convention over configuration</h3>
      <a href="#convention-over-configuration">
        
      </a>
    </div>
    <p>As you can see in the Cloud Gallery demo application, setting up a fragment-based micro-frontend requires quite a bit of configuration. A lot of this configuration is very mechanical and could be abstracted away via conventions and better tooling. Following productivity-focused precedence found in Ruby on Rails, and filesystem based routing meta-frameworks, we could make a lot of this configuration disappear.</p>
    <div>
      <h2>Try it yourself!</h2>
      <a href="#try-it-yourself">
        
      </a>
    </div>
    <p>There is still so much to dig into! Web applications have come a long way in recent years and their growth is hard to overstate. Traditional implementations of micro-frontends have had only mixed success in helping developers scale development and deployment of large applications. Cloudflare Workers, however, unlock new possibilities which can help us tackle many of the existing challenges and help us build better web applications.</p><p>Thanks to the <a href="https://workers.cloudflare.com/#plans">generous free plan</a> offered by Cloudflare Workers, you can <a href="https://github.com/cloudflare/workers-web-experiments/tree/main/cloud-gallery">check out the Gallery Demo code</a> and deploy it yourself.</p><p>If all of these sounds interesting to you, and you would like to work with us on improving the developer experience for Cloudflare Workers, we are also happy to share that <a href="https://boards.greenhouse.io/cloudflare/jobs/4619341">we are hiring</a>!</p> ]]></content:encoded>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Micro-frontends]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Serverless]]></category>
            <guid isPermaLink="false">irbcct7Wl9QqgfZhsBGew</guid>
            <dc:creator>Peter Bacon Darwin</dc:creator>
            <dc:creator>James Culveyhouse</dc:creator>
            <dc:creator>Igor Minar</dc:creator>
        </item>
        <item>
            <title><![CDATA[Internationalizing the Cloudflare Dashboard]]></title>
            <link>https://blog.cloudflare.com/internationalizing-the-cloudflare-dashboard/</link>
            <pubDate>Thu, 23 Jul 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare’s dashboard now supports four new language: Spanish (with country-specific locales: Chile, Ecuador, Mexico, Peru, and Spain), Brazilian Portuguese, Korean, and Traditional Chinese.  ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://dash.cloudflare.com/sign-up">Cloudflare’s dashboard</a> now supports four new languages (and multiple locales): Spanish (with country-specific locales: Chile, Ecuador, Mexico, Peru, and Spain), Brazilian Portuguese, Korean, and Traditional Chinese. Our customers are global and diverse, so in helping build a better Internet for everyone, it is imperative that we bring our products and services to customers in their native language.</p><p>Since last year Cloudflare has been hard at work internationalizing our dashboard. At the end of 2019, we launched our first language other than US English: German. At the end of March 2020, we released three additional languages: French, Japanese, and Simplified Chinese. If you want to start using the dashboard in any of these languages, you can change your language preference in the top right of the Cloudflare dashboard. The preference selected will be saved and used across all sessions.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6bPkaBuSElf3KVstMxueBb/96fe21b0b376c22d3ebdaa8395bf4e5e/image1-13.png" />
            
            </figure><p>In this blog post, I want to help those unfamiliar with internationalization and localization to better understand how it works. I also would like to tell the story of how we made internationalizing and localizing our application a standard and repeatable process along with sharing a few tips that may help you as you do the same.</p>
    <div>
      <h3>Beginning the journey</h3>
      <a href="#beginning-the-journey">
        
      </a>
    </div>
    <p>The first step in internationalization is externalizing all the strings in your application. In concrete terms this means taking any text that could be read by a user and extracting it from your application code into separate, stand-alone files. This needs to be done for a few reasons:</p><ul><li><p>It enables translation teams to work on translating these strings without needing to view or change any application code.</p></li><li><p>Most translators typically use Translation Management applications which automate aspects of the workflow and provide them with useful utilities (like translation memory, change tracking, and a number of useful parsing and formatting tools). These applications expect standardized text formats (such as JSON, XML, MD, or CSV files).</p></li></ul><p>From an engineering perspective, separating application code from translations allows for making changes to strings without re-compiling and/or re-deploying code. In our React based application, externalizing most of our strings boiled down to changing blocks of code like this:</p>
            <pre><code>&lt;Button&gt;Cancel&lt;/Button&gt;
&lt;Button&gt;Next&lt;/Button&gt;</code></pre>
            <p>Into this:</p>
            <pre><code>&lt;Button&gt;&lt;Trans id="signup.cancel" /&gt;&lt;/Button&gt;
&lt;Button&gt;&lt;Trans id="signup.next" /&gt;&lt;/Button&gt;
 
// And in a separate catalog.json file for en_US:
{
 "signup.cancel": "Cancel",
 "signup.next": "Next",
 // ...many more keys
}</code></pre>
            <p>The <code>&lt;Trans&gt;</code> component shown above is the fundamental i18n building block in our application. In this scheme, translated strings are kept in large dictionaries keyed by a translation ID. We call these dictionaries “translation catalogs”, and there are a set of translation catalogs for each language that we support.</p><p>At runtime, the <code>&lt;Trans&gt;</code> component looks up the translation in the correct catalog for the provided key and then inserts this translation into the page (via the DOM). All of an application's static text can be externalized with simple transformations like these.</p><p>However, when dynamic data needs to be intermixed with static text, the solution becomes a little more complicated. Consider the following seemingly straightforward example which is riddled with i18n landmines:</p>
            <pre><code>&lt;span&gt;You've selected { totalSelected } Page Rules.&lt;/span&gt;</code></pre>
            <p>It may be tempting to externalize this sentence by chopping it up into a few parts, like so:</p>
            <pre><code>&lt;span&gt;
 &lt;Trans id="selected.prefix" /&gt; {totalSelected } &lt;Trans id="pageRules" /&gt;
&lt;/span&gt;
 
// English catalog.json
{
 "selected.prefix": "You've selected",
 "pageRules": "Page Rules",
 // ...
}
 
// Japanese catalog.json
{
 "selected.prefix": "選択しました",
 "pageRules": "ページ ルール",
 // ...
}
 
// German catalog.json
{
 "selected.prefix": "Sie haben ausgewählt",
 "pageRules": "Page Rules",
 // ...
}
 
// Portuguese (Brazil) catalog.json
{
 "selected.prefix": "Você selecionou",
 "pageRules": "Page Rules",
 // ...
}
</code></pre>
            <p>This gets the job done and may even seem like an elegant solution. After all, both the <code>selected.prefix</code> and <code>pageRules.suffix</code> strings seem like they are destined to be reused. Unfortunately, chopping sentences up and then concatenating translated bits back together like this turns out to be the single largest pitfall when externalizing strings for internationalization.</p><p>The problem is that when translated, the various words that make up a sentence can be <a href="https://en.wikipedia.org/wiki/Inflection">morphed in different ways</a> based on context (singular vs plural contexts, due to word gender, subject/verb agreement, etc). This varies significantly from language to language, as does <a href="https://en.wikipedia.org/wiki/Word_order">word order</a>. For example in English, the sentence “We like them” follows a subject-verb-object order, while other languages might follow subject-object-verb (We them like), verb-subject-object (Like we them), or even other orderings. Because of these nuanced differences between languages, concatenating translated phrases into a sentence will almost always lead to localization errors.</p><p>The code example above contains actual translations we got back from our translation teams when we supplied them with “You’ve selected” and “Page Rules” as separate strings. Here’s how this sentence would look when rendered in the different languages:</p><table><tr><td><p><b>Language</b></p></td><td><p><b>Translation</b></p></td></tr><tr><td><p>Japanese</p></td><td><p>選択しました { totalSelected } ページ ルール。</p></td></tr><tr><td><p>German</p></td><td><p>Sie haben ausgewählt { totalSelected } Page Rules</p></td></tr><tr><td><p>Portuguese (Brazil)</p></td><td><p>Você selecionou { totalSelected } Page Rules.</p></td></tr></table><p>To compare, we also gave them the sentence as a single string using a placeholder for the variable, and here’s the result:</p><table><tr><td><p><b>Language</b></p></td><td><p><b>Translation</b></p></td></tr><tr><td><p>Japanese</p></td><td><p>%{ totalSelected } 件のページ ルールを選択しました。</p></td></tr><tr><td><p>German</p></td><td><p>Sie haben %{ totalSelected } Page Rules ausgewählt.</p></td></tr><tr><td><p>Portuguese (Brazil)</p></td><td><p>Você selecionou %{ totalSelected } Page Rules.</p></td></tr></table><p>As you can see, the translations differ for Japanese and German. We’ve got a localization bug on our hands.</p><p>So, In order to guarantee that translators will be able to convey the true meaning of your text with fidelity, it's important to keep each sentence intact as a single externalized string. Our <code>&lt;Trans&gt;</code> component allows for easy injection of values into template strings which allows us to do exactly that:</p>
            <pre><code>&lt;span&gt;
  &lt;Trans id="pageRules.selectedForDeletion" values={{ count: totalSelected }} /&gt;
&lt;/span&gt;

// English catalog.json
{
  "pageRules.selected": "You've selected %{ count } Page Rules.",
  // ...
}

// Japanese catalog.json
{
  "pageRules.selected": "%{ count } 件のページ ルールを選択しました。",
  // ...
}

// German catalog.json
{
  "pageRules.selected": "Sie haben %{ count } Page Rules ausgewählt.",
  // ...
}

// Portuguese(Brazil) catalog.json
{
  "pageRules.selected": "Você selecionou %{ count } Page Rules.",
  // ...
}</code></pre>
            <p>This allows translators to have the full context of the sentence, ensuring that all words will be translated with the correct inflection.</p><p>You may have noticed another potential issue. What happens in this example when <code>totalSelected</code> is just 1? With the above code, the user would see “You've selected 1-Page Rules for deletion”. We need to conditionally pluralize the sentence based on the value of our dynamic data. This turns out to be a fairly common use case, and our <code>&lt;Trans&gt;</code> component handles this automatically via the <code>smart_count</code> feature:</p>
            <pre><code>&lt;span&gt;
  &lt;Trans id="pageRules.selectedForDeletion" values={{ smart_count: totalSelected }} /&gt;
&lt;/span&gt;

// English catalog.json
{
  "pageRules.selected": "You've selected %{ smart_count } Page Rule. |||| You've selected %{ smart_count } Page Rules.",
}

// Japanese catalog.json
{
  "pageRules.selected": "%{ smart_count } 件のページ ルールを選択しました。 |||| %{ smart_count } 件のページ ルールを選択しました。",
}

// German catalog.json
{
  "pageRules.selected": "Sie haben %{ smart_count } Page Rule ausgewählt. |||| Sie haben %{ smart_count } Page Rules ausgewählt.",
}

// Portuguese (Brazil) catalog.json
{
  "pageRules.selected": "Você selecionou %{ smart_count } Page Rule. |||| Você selecionou %{ smart_count } Page Rules.",
}</code></pre>
            <p>Here, the singular and plural versions are delimited by <code>||||</code>. <code>&lt;Trans&gt;</code> will automatically select the right translation to use depending on the value of the passed in <code>totalSelected</code> variable.</p><p>Yet another stumbling block occurs when markup is mixed in with a block of text we'd like to externalize as a single string. For example, what if you need some phrase in your sentence to be a link to another page?</p>
            <pre><code>&lt;VerificationReminder&gt;
  Don't forget to &lt;Link&gt;verify your email address.&lt;/Link&gt;
&lt;/VerificationReminder&gt;</code></pre>
            <p>To solve for this use case, the <code>&lt;Trans&gt;</code> component allows for arbitrary elements to be injected into placeholders in a translation string, like so:</p>
            <pre><code>&lt;VerificationReminder&gt;
  &lt;Trans id="notification.email_verification" Components={[Link]} componentProps={[{ to: '/profile' }]} /&gt;
&lt;/VerificationReminder&gt;

// catalog.json
{
  "notification.email_verification": "Don't forget to &lt;0&gt;verify your email address.&lt;/0&gt;",
  // ...
}</code></pre>
            <p>In this example, the <code>&lt;Trans&gt;</code> component will replace placeholder elements (&lt;0&gt;, &lt;1&gt;, etc.) with instances of the component type located at that index in the <code>Components</code> array. It also passes along any data specified in <code>componentProps</code> to that instance. The example above would boil down to the following in React:</p>
            <pre><code>// en-US
&lt;VerificationReminder&gt;
  Don't forget to &lt;Link to="/profile"&gt;verify your email address.&lt;/Link&gt;
&lt;/VerificationReminder&gt;

// es-ES
&lt;VerificationReminder&gt;
  No olvide &lt;Link to="/profile"&gt;verificar la dirección de correo electrónico.&lt;/Link&gt;
&lt;/VerificationReminder&gt;</code></pre>
            
    <div>
      <h3>Safety third!</h3>
      <a href="#safety-third">
        
      </a>
    </div>
    <p>The functionality outlined above was enough for us to externalize our strings. However, it did at times result in bulky, repetitive code that was easy to mess up. A couple of pitfalls quickly became apparent.</p><p>The first was that small hard-coded strings were now easier to hide in plain sight, and because they weren't glaringly obvious to a developer until the rest of the page had been translated, the feedback loop in finding these was often days or weeks. A common solution to surfacing these issues is introducing a pseudo-localization mode into your application during development which will transform all properly internationalized strings by replacing each character with a similar looking unicode character.</p><p>For example <code>You've selected 3 Page Rules.</code> might be transformed to <code>Ýôú'Ʋè ƨèℓèçƭèδ 3 Þáϱè Rúℓèƨ</code>.</p><p>Another handy feature at your disposal in a pseudolocalization mode is the ability to shrink or lengthen all strings by a fixed amount in order to plan for content width differences. Here's the same pseudo-localized sentence increased in length by 50%: <code>Ýôú'Ʋè ƨèℓèçƭèδ 3 Þáϱè Rúℓèƨ. ℓôřè₥ ïƥƨú₥ δô.</code> This is useful in helping both engineers and designers spot places where content length could potentially be an issue. We first recognized this problem when rolling out support for German, which at times tends to have somewhat longer words than English.</p><p>This meant that in a lot of places the text in page elements would overflow, such as in this "Add" button:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2DcZK0IiIXGAW4naCbxqCr/b60a50a967f9eec972dc0bb9da72e181/image4-7.png" />
            
            </figure><p>There aren't a lot of easy fixes for these types of problems that don't compromise the user experience.</p><p>For best results, variable content width needs to be baked into the design itself. Since fixing these bugs often means sending it back upstream to request a new design, the process tends to be time-consuming. If you haven't given much thought to content design in general, an internationalization effort can be a good time to start. Having standards and consistency around the copy used for various elements in your app can not only cut down on the number of words that need translating, but also eliminate the need to think through the content length pitfalls of using a novel phrase.</p><p>The other pitfall we ran into was that the translation IDs — especially long and repetitive ones — are highly susceptible to typos.</p><p>Pop quiz, which of these translation keys will break our app: <code>traffic.load_balancing.analytics.filters.origin_health_title</code> or <code>traffic.load_balancing.analytics.filters.origin_heath_title</code>?</p><p>Nestled among hundreds of other lines of changes, these are hard to spot in code review. Most apps have a fallback so missing translations don't result in a page breaking error. As a result a bug like this might go unnoticed entirely if it's hidden well enough (in say, a help text fly out).</p><p>Fortunately, with a growing percentage of our codebase in TypeScript, we were able to leverage the type-checker to give developers feedback as they wrote the code. Here’s an example where our code editor is helpfully showing us a red underline to indicate that the ID property is invalid (due to the missing “l”):</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2W5zNv5D1bIaPGPGjiDpE0/a9abde771093fb812573a2c7fb3d58ea/image5-4.png" />
            
            </figure><p>Not only did it make the problems more obvious, but it also meant that violations would cause builds to fail, preventing bad code from entering the codebase.</p>
    <div>
      <h3>Scaling locale files</h3>
      <a href="#scaling-locale-files">
        
      </a>
    </div>
    <p>In the beginning, you'll probably start out with one translation file per locale that you support. In addition, the naming scheme you use for your keys can remain somewhat simple. As your app scales, your translation file will grow too large and need to be broken up into separate files. Files that are too large will overwhelm Translation Management applications, or if left unchecked, your code editor. All of our translation strings (not including keys), when lumped together into a single file, is around 50,000 words. For comparison, that's roughly the same size as a copy of "The Hitchhiker's Guide to the Galaxy" or "Slaughterhouse Five".</p><p>We break up our translations into a number of "catalog" files roughly corresponding to feature verticals (like Firewall or Cloudflare Workers). This works out well for our developers since it provides a predictable place to find strings, and keeps the line count of a translation catalog down to a manageable length. It also works out well for the outside translation teams since a single feature vertical is a good unit of work for a translator (or small team).</p><p>In addition to per-feature catalogs, we have a common catalog file to hold strings that are re-used throughout the application. It allows us to keep IDs short (<code>common.delete</code> vs <code>some_page.some_tab.some_feature.thing.delete</code>) and lowers the likelihood of duplication since developers habitually check the common catalog before adding new strings.</p>
    <div>
      <h3>Libraries</h3>
      <a href="#libraries">
        
      </a>
    </div>
    <p>So far we've talked at length about our <code>&lt;Trans&gt;</code> component and what it can do. Now, let's talk about how it's built.</p><p>Perhaps unsurprisingly, we didn't want to reinvent the wheel and come up with a base i18n library from scratch. Due to prior efforts to internationalize the legacy parts of our application written in Backbone, we were already using Airbnb's <a href="https://airbnb.io/polyglot.js/">Polyglot library</a>, a "tiny I18n helper library written in JavaScript" which, among other things, "provides a simple solution for interpolation and pluralization, based off of Airbnb’s experience adding I18n functionality to its Backbone.js and Node apps".</p><p>We took a look at a few of the most popular libraries that had been purpose-built for internationalizing React applications, but ultimately decided to stick with Polyglot. We created our <code>&lt;Trans&gt;</code> component to bridge the gap to React. We chose this direction for a few reasons:</p><ul><li><p>We didn't want to re-internationalize the legacy code in our application in order to migrate to a new i18n support library.</p></li><li><p>We also didn't want the combined overhead of supporting 2 different i18n schemes for new vs legacy code.</p></li><li><p>Writing our own trans component gave us the flexibility to write the interface we wanted. Since Trans is used just about everywhere, we wanted to make sure it was as ergonomic as possible to developers.</p></li></ul><p>If you're just getting started with i18n in a new React based web-app, react-intl and i18n-next are 2 popular libraries that supply a component similar to <code>&lt;Trans&gt;</code> described above.</p><p>The biggest pain point of the <code>&lt;Trans&gt;</code> component as outlined is that strings have to be kept in a separate file from your source code. Switching between multiple files as you author new code or modify existing features is just plain annoying. It's even more annoying if the translation files are kept far away in the directory structure, as they often need to be.</p><p>There are some new i18n libraries such as <a href="https://github.com/lingui/js-lingui">jslingui</a> that obviate this problem by taking an extraction based approach to handling translation catalogs. In this scheme, you still use a <code>&lt;Trans&gt;</code>component, but you keep your strings in the component itself, not a separate catalog:</p>
            <pre><code>&lt;span&gt;
  &lt;Trans&gt;Hmm... We couldn't find any matching websites.&lt;/Trans&gt;
&lt;/span&gt;</code></pre>
            <p>A tool that you run at build time then does the work of finding all of these strings and extracting then into catalogs for you. For example, the above would result in the following generated catalogs:</p>
            <pre><code>// locales/en_US.json
{
  "Hmm... We couldn't find any matching websites.": "Hmm... We couldn't find any matching websites.",
}

// locales/de_DE.json
{
  "Hmm... We couldn't find any matching websites.": "Hmm... Wir konnten keine übereinstimmenden Websites finden."
}</code></pre>
            <p>The obvious advantage to this approach is that we no longer have separate files! The other advantage is that there's no longer any need for type checking IDs since typos can't happen anymore.</p><p>However, at least for our use case, there were a few downsides.</p><p>First, human translators sometimes appreciate the context of the translation keys. It helps with organization, and it gives some clues about the string's purpose.</p><p>And although we no longer have to worry about typos in translation IDs, we're just as susceptible to slight copy mismatches (ex. "Verify your email" vs "Verify your e-mail"). This is almost worse, since in this case it would introduce a near duplication which would be hard to detect. We'd also have to pay for it.</p><p>Whichever tech stack you're working with, there are likely a few i18n libraries that can help you out. Which one to pick is highly dependent on technical constraints of your application and the context of your team's goals and culture.</p>
    <div>
      <h3>Numbers, Dates, and Times</h3>
      <a href="#numbers-dates-and-times">
        
      </a>
    </div>
    <p>Earlier when we talked about injecting data translated strings, we glossed over a major issue: the data we're injecting may also need to be formatted to conform to the user's local customs. This is true for dates, times, numbers, currencies and some other types of data.</p><p>Let's take our simple example from earlier:</p>
            <pre><code>&lt;span&gt;You've selected { totalSelected } Page Rules.&lt;/span&gt;</code></pre>
            <p>Without proper formatting, this will appear correct for small numbers, but as soon as things get into the thousands, localization problems will arise, since the way that digits are grouped and separated with symbols <a href="https://en.wikipedia.org/wiki/Decimal_separator">varies by culture</a>. Here's how three-hundred thousand and three hundredths is formatted in a few different locales:</p><table><tr><td><p><b>Language (Country)</b></p></td><td><p><b>Code</b></p></td><td><p><b>Formatted Date</b></p></td></tr><tr><td><p>German (Germany)</p></td><td><p>de-DE</p></td><td><p>300.000,03</p></td></tr><tr><td><p>English (US)</p></td><td><p>en-US</p></td><td><p>300,000.03</p></td></tr><tr><td><p>English (UK)</p></td><td><p>en-GB</p></td><td><p>300,000.03</p></td></tr><tr><td><p>Spanish (Spain)</p></td><td><p>es-ES</p></td><td><p>300.000,03</p></td></tr><tr><td><p>Spanish (Chile)</p></td><td><p>es-CL</p></td><td><p>300.000,03</p></td></tr><tr><td><p>French (France)</p></td><td><p>fr-FR</p></td><td><p>300 000,03</p></td></tr><tr><td><p>Hindi (India)</p></td><td><p>hi-IN</p></td><td><p>3,00,000.03</p></td></tr><tr><td><p>Indonesian (Indonesia)</p></td><td><p>in-ID</p></td><td><p>300.000,03</p></td></tr><tr><td><p>Japanese (Japan)</p></td><td><p>ja-JP</p></td><td><p>300,000.03</p></td></tr><tr><td><p>Korean (South Korea)</p></td><td><p>ko-KR</p></td><td><p>300,000.03</p></td></tr><tr><td><p>Portuguese (Brazil)</p></td><td><p>pt-BR</p></td><td><p>300.000,03</p></td></tr><tr><td><p>Portuguese (Portugal)</p></td><td><p>pt-PT</p></td><td><p>300 000,03</p></td></tr><tr><td><p>Russian (Russia)</p></td><td><p>ru-RU</p></td><td><p>300 000,03</p></td></tr></table><p>The way that dates are formatted <a href="https://en.wikipedia.org/wiki/Date_format_by_country">varies significantly from country to country</a>. If you've developed your UI mainly with a US audience in mind, you're probably displaying dates in a way that will feel foreign and perhaps unintuitive to users from just about any other place in the world. Among other things, date formatting can vary in terms of separator choice, whether single digits are zero padded, and in the way that the day, month, and year portions are ordered. Here's how the March 4th of the current year is formatted in a few different locales:</p><table><tr><td><p><b>Language (Country)</b></p></td><td><p><b>Code</b></p></td><td><p><b>Formatted Date</b></p></td></tr><tr><td><p>German (Germany)</p></td><td><p>de-DE</p></td><td><p>4.3.2020</p></td></tr><tr><td><p>English (US)</p></td><td><p>en-US</p></td><td><p>3/4/2020</p></td></tr><tr><td><p>English (UK)</p></td><td><p>en-GB</p></td><td><p>04/03/2020</p></td></tr><tr><td><p>Spanish (Spain)</p></td><td><p>es-ES</p></td><td><p>4/3/2020</p></td></tr><tr><td><p>Spanish (Chile)</p></td><td><p>es-CL</p></td><td><p>04-03-2020</p></td></tr><tr><td><p>French (France)</p></td><td><p>fr-FR</p></td><td><p>04/03/2020</p></td></tr><tr><td><p>Hindi (India)</p></td><td><p>hi-IN</p></td><td><p>4/3/2020</p></td></tr><tr><td><p>Indonesian (Indonesia)</p></td><td><p>in-ID</p></td><td><p>4/3/2020</p></td></tr><tr><td><p>Japanese (Japan)</p></td><td><p>ja-JP</p></td><td><p>2020/3/4</p></td></tr><tr><td><p>Korean (South Korea)</p></td><td><p>ko-KR</p></td><td><p>2020. 3. 4.</p></td></tr><tr><td><p>Portuguese (Brazil)</p></td><td><p>pt-BR</p></td><td><p>04/03/2020</p></td></tr><tr><td><p>Portuguese (Portugal)</p></td><td><p>pt-PT</p></td><td><p>04/03/2020</p></td></tr><tr><td><p>Russian (Russia)</p></td><td><p>ru-RU</p></td><td><p>04.03.2020</p></td></tr></table><p>Time format varies significantly as well. Here's how time is formatted in a few selected locales:</p><table><tr><td><p><b>Language (Country)</b></p></td><td><p><b>Code</b></p></td><td><p><b>Formatted Date</b></p></td></tr><tr><td><p>German (Germany)</p></td><td><p>de-DE</p></td><td><p>14:02:37</p></td></tr><tr><td><p>English (US)</p></td><td><p>en-US</p></td><td><p>2:02:37 PM</p></td></tr><tr><td><p>English (UK)</p></td><td><p>en-GB</p></td><td><p>14:02:37</p></td></tr><tr><td><p>Spanish (Spain)</p></td><td><p>es-ES</p></td><td><p>14:02:37</p></td></tr><tr><td><p>Spanish (Chile)</p></td><td><p>es-CL</p></td><td><p>14:02:37</p></td></tr><tr><td><p>French (France)</p></td><td><p>fr-FR</p></td><td><p>14:02:37</p></td></tr><tr><td><p>Hindi (India)</p></td><td><p>hi-IN</p></td><td><p>2:02:37 pm</p></td></tr><tr><td><p>Indonesian (Indonesia)</p></td><td><p>in-ID</p></td><td><p>14.02.37</p></td></tr><tr><td><p>Japanese (Japan)</p></td><td><p>ja-JP</p></td><td><p>14:02:37</p></td></tr><tr><td><p>Korean (South Korea)</p></td><td><p>ko-KR</p></td><td><p>오후 2:02:37</p></td></tr><tr><td><p>Portuguese (Brazil)</p></td><td><p>pt-BR</p></td><td><p>14:02:37</p></td></tr><tr><td><p>Portuguese (Portugal)</p></td><td><p>pt-PT</p></td><td><p>14:02:37</p></td></tr><tr><td><p>Russian (Russia)</p></td><td><p>ru-RU</p></td><td><p>14:02:37</p></td></tr></table>
    <div>
      <h3>Libraries for Handling Numbers, Dates, and Times</h3>
      <a href="#libraries-for-handling-numbers-dates-and-times">
        
      </a>
    </div>
    <p>Ensuring the correct format for all these types of data for all supported locales is no easy task. Fortunately, there are a number of mature, battle-tested libraries that can help you out.</p><p>When we kicked off our project, we were using the <a href="https://momentjs.com/">Moment.js</a> library extensively for date and time formatting. This handy library abstracts away the details of formatting dates to different lengths ("Jul 9th 20", "July 9th 2020", vs "Thursday"), displaying relative dates ("2 days ago"), amongst many other things. Since almost all of our dates were already being formatted via Moment.js for readability, and since Moment.js already has i18n support for numerous locales, it meant that we were able to flip a couple of switches and have properly localized dates with very little effort.</p><p>There are some strong criticisms of Moment.js (mainly bloat), but ultimately the benefits realized from switching to a lower footprint alternative when compared to the cost it would take to redo every date and time didn't add up.</p><p>Numbers were a very different story. We had, as you might imagine, thousands of raw, unformatted numbers being displayed throughout the dashboard. Hunting them down was a laborious and often manual process.</p><p>To handle the actual formatting of numbers, we used the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl">Intl API</a> (the Internationalization library defined by the ECMAScript standard):</p>
            <pre><code>var number = 300000.03;
var formatted = number.toLocaleString('hi-IN'); // 3,00,000.03
// This probably works in the browser you're using right now!</code></pre>
            <p>Fortunately, browser support for Intl has come quite a long way in recent years, with all modern browsers having full support.</p><p>Some modern JavaScript engines like V8 have even moved away from self-hosted JavaScript implementations of these libraries in favor of C++ based builtins, <a href="https://v8.dev/blog/intl">resulting in significant speedup</a>.</p><p>Support for older browsers can be somewhat lacking, however. Here's a <a href="https://intl-formatting.jculvey.workers.dev/">simple demo site</a> (<a href="https://github.com/jculvey/intl-formatting-worker">source code</a>) that’s built with <a href="https://workers.cloudflare.com/">Cloudflare Workers</a> that shows how dates, times, and numbers are rendered in a hand-full of locales.</p><p>Some combinations of old browsers and OS's will yield less than ideal results. For example, here's how the same dates and times from above are rendered on Windows 8 with IE 10:</p><table>
<tbody>
<tr>
<td><img src="http://staging.blog.mrk.cfdata.org/content/images/2020/07/image3-10.png" /></td>
<td><img src="http://staging.blog.mrk.cfdata.org/content/images/2020/07/image2-7.png" /></td>
</tr>
</tbody>
</table><p>If you need to support older browsers, this can be solved with a polyfill.</p>
    <div>
      <h3>Translating</h3>
      <a href="#translating">
        
      </a>
    </div>
    <p>With all strings externalized, and all injected data being carefully formatted to locale specific standards, the bulk of the engineering work is complete. At this point, we can now claim that we’ve <b>internationalized</b> our application, since we’ve adapted it in a way that makes it easy to localize.</p><p>Next comes the process of <b>localization</b> where we actually create varying content based on the user’s language and cultural norms.</p><p>This is no small feat. Like we mentioned before, the strings in our application added together are the size of a small novel. It takes a significant amount of coordination and human expertise to create a translated copy that both captures the information with fidelity and speaks to the user in a familiar way.</p><p>There are many ways to handle the translation work: leveraging multilingual staff members, contracting the work-out to individual translators, agencies, or even going all in and hiring teams of in-house translators. Whatever the case may be, there needs to be a smooth process for both workflow signalling and moving assets between the translation and development teams.</p><p>A healthy i18n program will provide developers with black-box interface with the process — they put new strings in a translation catalog file and commit the change, and without any more effort on their part, the feature code they wrote is available in production for all supported locales a few days later. Similarly, in a well run process translators will remain blissfully unaware of the particulars of the development process and application architecture. They receive files that easily load in their tools and clearly indicate what translation work needs to be done.</p><p>So, how does it actually work in practice?</p><p>We have a set of automated scripts that can be run on-demand by the localization team to package up a snapshot of our localization catalogs for all supported languages. During this process, a few things happen:</p><ul><li><p>JSON files are generated from catalog files authored in TypeScript</p></li><li><p>If any new catalog files were added in English, placeholder copies are created for all other supported languages.</p></li><li><p>Placeholder strings are added for all languages when new strings are added to our base catalog</p></li></ul><p>From there, the translation catalogs are uploaded to the Translation Management system via the UI or automated calls to the API. Before handing it off to translators, the files are pre-processed by comparing each new string against a Translation Memory (a cache of previously translated strings and substrings). If a match is found, the existing translation is used. Not only does this save cost by not re-translating strings, but it improves quality by ensuring that previously reviewed and approved translations are used when possible.</p><p>Suppose your locale files end up looking something like this:</p>
            <pre><code>{
 "verify.button": "Verify Email",
 "other.verify.button": "Verify Email",
 "verify.proceed.link": "Verify Email to proceed",
 // ...
}</code></pre>
            <p>Here, we have strings that are duplicated verbatim, as well as sub-strings that are copied. Translation services are billed by the word — you don’t want to pay for something twice and run the risk of a consistency issue arising. To this end, having a well-maintained Translation Memory will ensure that these strings are taken care of in the pre-translation steps before translators even see the file.</p><p>Once the translation job is marked as ready, it can take translation teams anywhere from hours to weeks to complete return translated copies depending on a number of factors such as the size of the job, the availability of translators, and the contract terms. The concerns of this phase could constitute another blog article of similar length: sourcing the right translation team, controlling costs, ensuring quality and consistency, making sure the company’s brand is properly conveyed, etc. Since the focus of this article is largely technical, we’ll gloss over the details here, but make no mistake -- getting this part wrong will tank your entire effort, even if you’ve achieved your technical objectives.</p><p>After translation teams signal that new files are ready for pickup, the assets are pulled from the server and unpacked into their correct locations in the application code. We then run a suite of automated checks to make sure that all files are valid and free of any formatting issues.</p><p>An optional (but highly recommended) step takes place at this stage — in-context review. A team of translation reviewers then look at the translated output in context to make sure everything looks perfect in its finalized state. Having support staff that are both highly proficient with the product and fluent in the target language are especially useful in this effort. Shoutout to all our team members from around the company that have taken the time and effort to do this. To make this possible for outside contractors, we prepare special preview versions of our app that allow them to test with development mode locales enabled.</p><p>And there you have it, everything it takes to deliver a localized version of your application to your users all around the world.</p>
    <div>
      <h3>Continual Localization</h3>
      <a href="#continual-localization">
        
      </a>
    </div>
    <p>It would be great to stop here, but what we’ve discussed up until this point is the effort required to do it once. As we all know, code changes. New strings will be gradually added, modified, and deleted over the course of ti me as new features are launched and tweaked.</p><p>Since translation is a highly human process that often involves effort from people in different corners of the world, there is a lower bound to the timeframe in which turnover is possible. Since our release cadence (daily) is often faster than this turnover rate (2-5 days), it means that developers making changes to features have to make a choice: slow down to match this cadence, or ship slightly ahead of the localization schedule without full coverage.</p><p>In order to ensure that features shipping ahead of translations don’t cause application-breaking errors, we fall back to our base locale (en_US) if a string doesn’t exist for the configured language.</p><p>Some applications have a slightly different fallback behavior: displaying raw translation keys (perhaps you've seen <i>some.funny.dot.delimited.string</i> in an app you're using). There's a tradeoff between velocity and correctness here, and we chose to optimize for velocity and minimal overhead. In some apps correctness is important enough to slow down cadence for i18n. In our case it wasn't.</p>
    <div>
      <h3>Finishing Touches</h3>
      <a href="#finishing-touches">
        
      </a>
    </div>
    <p>There are a few more things we can do to optimize the user experience in our newly localized application.</p><p>First, we want to make sure there isn’t any performance degradation. If our application made the user fetch all of its translated strings before rendering the page, this would surely happen. So, in order to keep everything running smoothly, the translation catalogs are fetched asynchronously and only as the application needs them to render some content on the page. This is easy to accomplish nowadays with the code splitting features available in module bundlers that support dynamic import statements such as <a href="https://parceljs.org/code_splitting.html">Parcel</a> or <a href="https://webpack.js.org/guides/code-splitting/">Webpack</a>.</p><p>We also want to eliminate any friction the user might experience with needing to constantly select their desired language when visiting different Cloudflare properties. To this end, we made sure that any language preference a user selects on our <a href="https://www.cloudflare.com/fr-fr/">marketing site</a> or our <a href="https://support.cloudflare.com/hc/fr-fr">support site</a> persists as they navigate to and from our <a href="https://dash.cloudflare.com/login?lang=fr-fr">dashboard</a> (all links are in French to belabor the point).</p>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>It’s been an exciting journey, and we’ve learned a lot from the process. It’s difficult (perhaps impossible) to call an i18n project truly complete.  Expanding into new languages will surface slippery bugs and expose new challenges. Budget pressure will challenge you to find ways of cutting costs and increasing efficiency. In addition, you will discover ways in which you can enhance the localized experience even more for users.</p><p>There’s a long list of things we’d like to improve upon, but here are some of the highlights:</p><ul><li><p>Collation. String comparison is language sensitive, and as such, the code you’ve written to lexicographically sort lists and tables of data in your app is probably doing the wrong thing for some of your users. This is especially apparent in languages that use logo graphic writing systems (such as Chinese or Japanese) as opposed to languages that use alphabets (like English or Spanish).</p></li><li><p>Support for <a href="https://en.wikipedia.org/wiki/Right-to-left">right-to-left languages</a> like Arabic and Hebrew.</p></li><li><p>Localizing API responses is harder than localizing static copy in your user interface, as it takes a coordinated effort between teams. In the age of microservices, finding a solution that works well across the myriad of tech stacks that power each service can be very challenging.</p></li><li><p>Localizing maps. We’ll be working on making sure all content in our map-based visualizations is translated.</p></li><li><p>Machine translation has come a long way in recent years, but not far enough to churn our translations unsupervised. We would however like to experiment more with using machine translation as a first pass that translation reviewers then edit for correctness and tone.</p></li></ul><p>I hope you have enjoyed this overview of how Cloudflare internationalized and localized our dashboard.  Check out <a href="https://www.cloudflare.com/careers/jobs/">our careers page</a> for more information on full-time positions and internship roles across the globe.</p> ]]></content:encoded>
            <category><![CDATA[Dashboard]]></category>
            <category><![CDATA[Product News]]></category>
            <guid isPermaLink="false">7MdK7VijKW2yLm60JTPzvN</guid>
            <dc:creator>James Culveyhouse</dc:creator>
        </item>
    </channel>
</rss>