diff --git a/src/content/guides/modern-web-platform.mdx b/src/content/guides/modern-web-platform.mdx
new file mode 100644
index 000000000000..ba31c73fab32
--- /dev/null
+++ b/src/content/guides/modern-web-platform.mdx
@@ -0,0 +1,199 @@
+---
+title: Modern Web Platform
+description: Web Components, import maps, and PWAs with webpack 5.
+sort: 26
+contributors:
+ - phoekerson
+---
+
+This guide describes practical webpack patterns for **Web Components**, **Import Maps**, and **Progressive Web Apps** (PWAs) with **Service Workers**. Each section states the problem, shows a minimal configuration you can copy, and notes current limits relative to future webpack improvements.
+
+T> Familiarity with [code splitting](/guides/code-splitting/), [caching](/guides/caching/) (`[contenthash]`), and the [`SplitChunksPlugin`](/plugins/split-chunks-plugin/) helps.
+
+## Web Components with webpack
+
+### Problem
+
+If more than one JavaScript bundle executes `customElements.define()` for the same tag name, the browser throws [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException): `Failed to execute 'define' on 'CustomElementRegistry'`. That often happens when the module that registers an element is duplicated: separate entry points or async chunks each contain a copy of the registration code, so two bundles both run `define` for the same tag.
+
+### Approach
+
+Use [`optimization.splitChunks`](/configuration/optimization/#optimizationsplitchunks) so the module that defines the element lives in a **single shared chunk** loaded once. Adjust `cacheGroups` so your element definitions (or a dedicated folder such as `src/elements/`) are forced into one chunk. See [Prevent Duplication](/guides/code-splitting/#prevent-duplication) for the general idea.
+
+**webpack.config.js**
+
+```js
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+export default {
+ entry: {
+ main: "./src/main.js",
+ admin: "./src/admin.js",
+ },
+ output: {
+ filename: "[name].js",
+ path: path.resolve(__dirname, "dist"),
+ clean: true,
+ },
+ optimization: {
+ splitChunks: {
+ chunks: "all",
+ cacheGroups: {
+ // Put shared custom element modules in one async chunk.
+ customElements: {
+ test: /[\\/]src[\\/]elements[\\/]/,
+ name: "custom-elements",
+ chunks: "all",
+ enforce: true,
+ },
+ },
+ },
+ },
+};
+```
+
+Ensure both entries import the same registration module (for example `./elements/my-element.js`) so webpack can emit one `custom-elements.js` chunk instead of inlining duplicate registration in `main` and `admin`.
+
+### Limitations and future work
+
+Splitting alone does not change **browser** rules: the tag name must still be registered exactly once per document. Webpack does not yet provide a first-class “register this custom element once” primitive beyond chunk graph control. Native support for deduplicating custom element registration across the build is **planned**; until then, rely on shared chunks and a single registration module.
+
+## Import Maps with webpack
+
+### Problem
+
+[Import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap) let the browser resolve **bare specifiers** (`import "lodash-es"` from `importmap.json` or an inline `
+
+```
+
+W> [`experiments.outputModule`](/configuration/experiments/#experimentsoutputmodule) and [`output.module`](/configuration/output/#outputmodule) are still experimental. Check the latest [webpack release notes](https://github.com/webpack/webpack/releases) before relying on them in production.
+
+### Limitations and future work
+
+Webpack **does not** emit or update `importmap.json` for you. You must maintain the map so specifiers and URLs stay aligned with `externals` and your server layout. Automatic import-map generation is **not** available in webpack 5 today; future tooling may reduce this manual step.
+
+## Progressive Web Apps (PWA) and Service Workers
+
+### Problem
+
+Long-lived caching requires **stable URLs** for HTML but **versioned URLs** for scripts and styles. Using [`[contenthash]`](/guides/caching/) in `output.filename` changes those URLs every build. A **service worker** precache list must list the **exact** URLs after each build, or offline shells will point at missing files.
+
+The [`workbox-webpack-plugin`](/guides/progressive-web-application/) **`GenerateSW`** plugin generates an entire service worker for you. That is convenient, but when you need **full control** over service worker code (custom routing, `skipWaiting` behavior, or coordination with `[contenthash]` and other plugins), **`InjectManifest`** is appropriate: you write the worker, and Workbox injects the precache manifest at build time from webpack’s asset list.
+
+### Approach
+
+Use `[contenthash]` for emitted assets and add **`InjectManifest`** from `workbox-webpack-plugin`. Your source template imports `workbox-precaching` and calls `precacheAndRoute(self.__WB_MANIFEST)`; the plugin replaces `self.__WB_MANIFEST` with the list of webpack assets (including hashed filenames).
+
+Install:
+
+```bash
+npm install workbox-webpack-plugin workbox-precaching --save-dev
+```
+
+**webpack.config.js**
+
+```js
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import HtmlWebpackPlugin from "html-webpack-plugin";
+import { InjectManifest } from "workbox-webpack-plugin";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+export default {
+ entry: "./src/index.js",
+ output: {
+ filename: "[name].[contenthash].js",
+ path: path.resolve(__dirname, "dist"),
+ clean: true,
+ },
+ plugins: [
+ new HtmlWebpackPlugin({ title: "PWA + content hashes" }),
+ new InjectManifest({
+ swSrc: path.resolve(__dirname, "src/service-worker.js"),
+ swDest: "service-worker.js",
+ }),
+ ],
+};
+```
+
+**src/service-worker.js** (precache template)
+
+```js
+import { precacheAndRoute } from "workbox-precaching";
+
+// Replaced at build time with webpack's precache manifest (hashed asset URLs).
+precacheAndRoute(globalThis.__WB_MANIFEST);
+```
+
+Register the emitted `service-worker.js` from your app (for example in `src/index.js`) with `navigator.serviceWorker.register("/service-worker.js")`, served from `dist/` with the correct scope.
+
+### Limitations and future work
+
+You must keep **`InjectManifest`** in sync with your output filenames and plugins; `GenerateSW` remains the simpler path when you do not need a custom worker. Webpack does not ship a built-in service worker precache generator; tighter integration with hashed assets may arrive in future releases. Until then, Workbox’s **`InjectManifest`** is a well-supported way to align `[contenthash]` output with precaching.