{
const rawProps = element.querySelector("script[type='application/json']")?.textContent;
const props = rawProps && devalue.parse(rawProps);
- const hydrate = element.tagName.toLowerCase() === "jsm-hydrate";
+ const hydrate = Boolean(element.dataset.clientOnly);
const { default: Component } = await import(entry);
@@ -74,7 +74,7 @@ const hydrateReactComponent = async (root: HTMLElement) => {
/** Hydrates all React components of `root`. */
export const hydrateReactComponents = () => {
- for (const element of document.querySelectorAll("jsm-hydrate, jsm-render")) {
+ for (const element of document.querySelectorAll("jsm-island")) {
hydrateReactComponent(element as HTMLElement);
}
};
diff --git a/javascript-modules-library/README.md b/javascript-modules-library/README.md
index fbc19779..42116b48 100644
--- a/javascript-modules-library/README.md
+++ b/javascript-modules-library/README.md
@@ -4,7 +4,25 @@ This library exposes common types and utility functions for JavaScript modules r
## Rendering components
-### `RenderInBrowser`
+### `Island`
+
+This component creates an island of interactivity on the page, following the [Island Architecture](https://www.jahia.com/blog/leveraging-the-island-architecture-in-jahia-cms) paradigm.
+
+```tsx
+
+
If MyComponent takes children, it will receive them here.
+
+```
+
+It takes an optional `clientOnly` prop:
+
+- By default or when set to `false`, the component will be rendered on the server and hydrated in
+ the browser. In this case, children are passed to the component.
+- When set to `true`, the component will be rendered only in the browser, skipping the server-side
+ rendering step. This is useful for components that cannot be rendered on the server. In this
+ case, children are used as a placeholder until the component is hydrated.
+
+### `RenderInBrowser` (deprecated)
This component is used to render a React component in the browser. It is used to render a component in the browser, skipping the server-side rendering step. Provided children, if any, will be rendered on the server and replaced by the component in the browser.
@@ -14,7 +32,9 @@ This component is used to render a React component in the browser. It is used to
```
-### `HydrateInBrowser`
+This component is deprecated and will be removed in the future major version. Use the `Island` component with the `clientOnly` prop instead.
+
+### `HydrateInBrowser` (deprecated)
This component is used to hydrate a React component in the browser. It will be rendered on the server then hydrated in the browser. Provided children, if any, will be children of the component.
@@ -24,6 +44,8 @@ This component is used to hydrate a React component in the browser. It will be r
```
+This component is deprecated and will be removed in the future major version. Use the `Island` component instead.
+
### `Render`
This component renders a Jahia component out of a node or a JS object.
diff --git a/javascript-modules-library/src/components/render/HydrateInBrowser.tsx b/javascript-modules-library/src/components/render/HydrateInBrowser.tsx
index 94291092..4cc68bf5 100644
--- a/javascript-modules-library/src/components/render/HydrateInBrowser.tsx
+++ b/javascript-modules-library/src/components/render/HydrateInBrowser.tsx
@@ -1,10 +1,11 @@
-import InBrowser from "./internal/InBrowser.js";
+import { Island } from "./Island.js";
/**
* Will render the given React component server side and hydrate it in the browser to make it
* dynamic. Be careful, the component will not have access to the
* '@jahia/javascript-modules-library' library from the browser.
*
+ * @deprecated Use `` instead.
* @returns The component to be hydrated in the browser
*/
export function HydrateInBrowser(
@@ -22,6 +23,7 @@ export function HydrateInBrowser(
}>,
): React.JSX.Element;
+/** @deprecated Use `` instead. */
export function HydrateInBrowser(
props: Readonly<{
/** The React component. */
@@ -30,6 +32,7 @@ export function HydrateInBrowser(
}>,
): React.JSX.Element;
+/** @deprecated Use `` instead. */
export function HydrateInBrowser(
props: Readonly<{
/** The React component. */
@@ -45,6 +48,7 @@ export function HydrateInBrowser(
): React.JSX.Element;
// Without props declaration
+/** @deprecated Use `` instead. */
export function HydrateInBrowser(
props: Readonly<{
/** The React component. */
@@ -53,6 +57,7 @@ export function HydrateInBrowser(
): React.JSX.Element;
// Implementation
+/** @deprecated Use `` instead. */
export function HydrateInBrowser({
child: Child,
props,
@@ -62,9 +67,6 @@ export function HydrateInBrowser({
props?: T & React.JSX.IntrinsicAttributes;
children?: React.ReactNode;
}>): React.JSX.Element {
- return (
-
- {children}
-
- );
+ // @ts-expect-error The type of Island is too complex for TypeScript to infer correctly
+ return ;
}
diff --git a/javascript-modules-library/src/components/render/Island.tsx b/javascript-modules-library/src/components/render/Island.tsx
new file mode 100644
index 00000000..9ff21d76
--- /dev/null
+++ b/javascript-modules-library/src/components/render/Island.tsx
@@ -0,0 +1,219 @@
+import * as devalue from "devalue";
+import i18n from "i18next";
+import { createElement, type ComponentType, type ReactNode, type JSX } from "react";
+import { I18nextProvider } from "react-i18next";
+import sharedLibFiles from "virtual:shared-lib-files";
+import { useServerContext } from "../../hooks/useServerContext.js";
+import { buildModuleFileUrl } from "../../utils/urlBuilder/urlBuilder.js";
+import { AddResources } from "../AddResources.js";
+
+/**
+ * This component creates an island of interactivity on the page, following the [Island
+ * Architecture](https://www.jahia.com/blog/leveraging-the-island-architecture-in-jahia-cms)
+ * paradigm.
+ *
+ * ```tsx
+ *
+ *
If MyComponent takes children, it will receive them here.
+ * ;
+ * ```
+ *
+ * It takes an optional `clientOnly` prop:
+ *
+ * - By default or when set to `false`, the component will be rendered on the server and hydrated in
+ * the browser. In this case, children are passed to the component.
+ * - When set to `true`, the component will be rendered only in the browser, skipping the server-side
+ * rendering step. This is useful for components that cannot be rendered on the server. In this
+ * case, children are used as a placeholder until the component is hydrated.
+ */
+// @ts-expect-error TS complains that the signature does not match the implementation, but it does
+export function Island({}: {
+ /** The React component to render. */
+ component: ComponentType;
+} & (keyof Omit extends never
+ ? {
+ props?: never; // If the component has no properties (other than children), none can be passed
+ }
+ : Omit extends Required>
+ ? {
+ // If at least one property of component are mandatory, they must be passed
+ /** Props to forward to the component. */
+ props: Omit;
+ }
+ : {
+ // If all properties of component are optional, they may be passed or not
+ /** Props to forward to the component. */
+ props?: Omit;
+ }) &
+ (Props extends { children: infer Children }
+ ? // If the component has mandatory children, it cannot be client-only
+ {
+ /**
+ * If false or undefined, the component will be rendered on the server. If true, server-side
+ * rendering will be skipped.
+ */
+ clientOnly?: false;
+ /** The children to render inside the component. */
+ children: Children;
+ }
+ : Props extends { children?: infer Children }
+ ? // If the component has optional children, it may be client-only or not
+ | {
+ // In SSR mode, the children are passed to the component and must be of the correct type
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly?: false;
+ /** The children to render inside the component. */
+ children?: Children;
+ }
+ | {
+ // In CSR mode, the children are used as a placeholder and may be of any type
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly: true;
+ /** Placeholder content until the component is rendered on the client. */
+ children?: ReactNode;
+ }
+ : // If the component has no children, it may be client-only or not
+ | {
+ // In SSR mode, the component cannot have children
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly?: false;
+ // Prevent children from being passed to the component
+ children?: never;
+ }
+ | {
+ // In CSR mode, the children are used as a placeholder and may be of any type
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly: true;
+ /** Placeholder content until the component is rendered on the client. */
+ children?: ReactNode;
+ })): ReactNode;
+
+// We use an overload rather than a single function because some props (e.g. children) are not always defined
+export function Island({
+ component: Component,
+ props,
+ clientOnly,
+ children,
+}: Readonly<{
+ component: ComponentType;
+ props?: any;
+ clientOnly?: boolean;
+ children?: ReactNode;
+}>): ReactNode {
+ const { bundleKey, currentResource } = useServerContext();
+ const language = currentResource.getLocale().getLanguage();
+
+ /** Base path to all javascript-modules-engine resources. */
+ const base = buildModuleFileUrl("javascript", { moduleName: "javascript-modules-engine" });
+
+ /** JS entry point to the client bundle loader. */
+ // @ts-expect-error __filename is added by the vite plugin
+ const entry = buildModuleFileUrl(`${Component.__filename}.js`);
+
+ /**
+ * All translations that can be used by the component on the client side. (only ship the current
+ * language)
+ */
+ const i18nResourceBundle = i18n.getResourceBundle(language, bundleKey);
+
+ return (
+ <>
+ ``)
+ .join("")
+ }
+ `
+ }
+ />
+ `}
+ />
+ {i18nResourceBundle && (
+
+ ${devalue.stringify({ [language]: i18nResourceBundle })}
+ `
+ }
+ />
+ )}
+ {/* The import map must come first in the page so that all imports are resolved correctly */}
+ `}
+ />
+
+ {
+ // We use a custom element to create the hydration marker, rather than a div or a span,
+ // to prevent a broken DOM structure in the browser. (e.g. a `
` inside a `
`)
+ createElement("jsm-island", {
+ "style": { display: "contents" },
+ "data-client-only": clientOnly ? 1 : undefined,
+ "data-src": entry,
+ "data-lang": language,
+ "data-bundle": bundleKey,
+ "children": [
+ props !== undefined && (
+
+ ),
+ clientOnly ? (
+ children
+ ) : (
+
+
+ {createElement("jsm-children", { style: { display: "contents" }, children })}
+
+
+ ),
+ ],
+ })
+ }
+ >
+ );
+}
diff --git a/javascript-modules-library/src/components/render/RenderInBrowser.tsx b/javascript-modules-library/src/components/render/RenderInBrowser.tsx
index e4f0e9f6..973e57af 100644
--- a/javascript-modules-library/src/components/render/RenderInBrowser.tsx
+++ b/javascript-modules-library/src/components/render/RenderInBrowser.tsx
@@ -1,9 +1,10 @@
-import InBrowser from "./internal/InBrowser.js";
+import { Island } from "./Island.js";
/**
* Will render the given React component in the browser. Be careful, the component will not have
* access to the '@jahia/javascript-modules-library' library from the browser.
*
+ * @deprecated Use `` instead.
* @returns The component to be rendered in the browser
*/
export function RenderInBrowser(
@@ -21,6 +22,7 @@ export function RenderInBrowser(
}>,
): React.JSX.Element;
+/** @deprecated Use `` instead. */
export function RenderInBrowser(
props: Readonly<{
/** The React component */
@@ -29,6 +31,7 @@ export function RenderInBrowser(
}>,
): React.JSX.Element;
+/** @deprecated Use `` instead. */
export function RenderInBrowser({
child: Child,
props,
@@ -38,9 +41,6 @@ export function RenderInBrowser({
props?: T & React.JSX.IntrinsicAttributes;
children?: React.ReactNode;
}>): React.JSX.Element {
- return (
-
- {children}
-
- );
+ // @ts-expect-error The type of Island is too complex for TypeScript to infer correctly
+ return ;
}
diff --git a/javascript-modules-library/src/components/render/internal/InBrowser.tsx b/javascript-modules-library/src/components/render/internal/InBrowser.tsx
deleted file mode 100644
index 01843a5e..00000000
--- a/javascript-modules-library/src/components/render/internal/InBrowser.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import * as devalue from "devalue";
-import i18n from "i18next";
-import { createElement } from "react";
-import { I18nextProvider } from "react-i18next";
-import sharedLibFiles from "virtual:shared-lib-files";
-import { useServerContext } from "../../../hooks/useServerContext.js";
-import { buildModuleFileUrl } from "../../../utils/urlBuilder/urlBuilder";
-import { AddResources } from "../../AddResources.js";
-
-/**
- * Import map to allow bare identifiers (e.g. `import { useState } from "react"`) to be imported
- * from our bundle in the browser.
- *
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap}
- */
-const getImportMap = (base: string) =>
- /* HTML */ ``;
-
-const getClientI18nStoreScript = (bundleKey: string, lang: string) => {
- const i18nResourceBundle = i18n.getResourceBundle(lang, bundleKey);
- if (i18nResourceBundle) {
- const filteredI18nStore = { [lang]: i18nResourceBundle };
-
- return /* HTML */ ``;
- }
-};
-
-function InBrowser({
- child: Child,
- props,
- ssr,
- children,
-}: Readonly<{
- child: React.ComponentType;
- props: T & React.JSX.IntrinsicAttributes;
- ssr?: boolean;
- children?: React.ReactNode;
-}>): React.JSX.Element {
- const { bundleKey, currentResource } = useServerContext();
- const language = currentResource.getLocale().getLanguage();
-
- const i18nScript = getClientI18nStoreScript(bundleKey, language);
-
- /** Base path to all javascript-modules-engine resources. */
- const engineBase = buildModuleFileUrl("javascript", { moduleName: "javascript-modules-engine" });
-
- /** JS entry point to the client bundle loader. */
- // @ts-expect-error __filename is added by a vite plugin
- const entry = buildModuleFileUrl(`${Child.__filename}.js`);
-
- return (
- <>
- {/* Insert modulepreload hints to preload files deep in the dependency tree */}
- ``)
- .join("")}
- />
- `}
- />
- {/* The import map must come first in the page so that all imports are resolved correctly */}
-
- `}
- />
-
- {
- // We use a custom element to create the hydration marker, rather than a div or a span,
- // to prevent a broken DOM structure in the browser. (e.g. a `
` inside a `
`)
- createElement(ssr ? "jsm-hydrate" : "jsm-render", {
- "style": { display: "contents" },
- "data-src": entry,
- "data-lang": language,
- "data-bundle": bundleKey,
- "children": [
- props !== undefined && (
-
- ),
- ssr ? (
-
-
- {createElement("jsm-children", { style: { display: "contents" }, children })}
-
-
- ) : (
- children
- ),
- ],
- })
- }
-
- {i18nScript && (
-
- )}
- >
- );
-}
-
-export default InBrowser;
diff --git a/javascript-modules-library/src/index.ts b/javascript-modules-library/src/index.ts
index a1726a96..cd31a7ff 100644
--- a/javascript-modules-library/src/index.ts
+++ b/javascript-modules-library/src/index.ts
@@ -1,4 +1,5 @@
// Rendering components
+export { Island } from "./components/render/Island.js";
export { RenderInBrowser } from "./components/render/RenderInBrowser.js";
export { HydrateInBrowser } from "./components/render/HydrateInBrowser.js";
export { Render } from "./components/render/Render.js";
diff --git a/javascript-modules-library/tsconfig.json b/javascript-modules-library/tsconfig.json
index 6dce260e..ea6af67f 100644
--- a/javascript-modules-library/tsconfig.json
+++ b/javascript-modules-library/tsconfig.json
@@ -22,7 +22,8 @@
"baseUrl": ".",
"paths": {
"*": ["target/types/*"]
- }
+ },
+ "verbatimModuleSyntax": true
},
"include": ["src/**/*.js", "src/**/*.mjs", "src/**/*.ts"],
"exclude": ["node_modules", "**/*.spec.ts", "**/*.spec.tsx"]
From 6a5813b7482ecd1ce02442b3ca72ea9ac0affec0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gautier=20Ben=20A=C3=AFm?=
Date: Thu, 7 Aug 2025 13:15:46 +0200
Subject: [PATCH 02/15] qa
---
.../src/client-javascript/react/index.tsx | 2 +-
.../src/components/render/Island.tsx | 11 +++++++----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/javascript-modules-engine/src/client-javascript/react/index.tsx b/javascript-modules-engine/src/client-javascript/react/index.tsx
index 7401a399..ecc35d7d 100644
--- a/javascript-modules-engine/src/client-javascript/react/index.tsx
+++ b/javascript-modules-engine/src/client-javascript/react/index.tsx
@@ -44,7 +44,7 @@ const load = async (element: HTMLElement) => {
}
const rawProps = element.querySelector("script[type='application/json']")?.textContent;
- const props = rawProps && devalue.parse(rawProps);
+ const props = rawProps ? devalue.parse(rawProps) : {};
const hydrate = Boolean(element.dataset.clientOnly);
const { default: Component } = await import(entry);
diff --git a/javascript-modules-library/src/components/render/Island.tsx b/javascript-modules-library/src/components/render/Island.tsx
index 9ff21d76..901e00f4 100644
--- a/javascript-modules-library/src/components/render/Island.tsx
+++ b/javascript-modules-library/src/components/render/Island.tsx
@@ -136,7 +136,11 @@ export function Island({
targetTag="head"
inlineResource={
/* HTML */ `${
- // Module preload hints for shared libraries, deep in the dependency tree
+ /**
+ * Module preload hints for shared libraries, deep in the dependency tree
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/modulepreload
+ */
sharedLibFiles
.map((file) => ``)
.join("")
@@ -147,7 +151,7 @@ export function Island({
* Import map to allow bare identifiers (e.g. `import { useState } from "react"`) to
* be imported from our bundle in the browser.
*
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap}
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap
*/
imports: {
// Explicitly exposed:
@@ -171,7 +175,7 @@ export function Island({
/>
{i18nResourceBundle && (
)}
- {/* The import map must come first in the page so that all imports are resolved correctly */}
Date: Thu, 7 Aug 2025 13:18:44 +0200
Subject: [PATCH 03/15] copiloted
---
.../src/react/server/views/testI18n/TestI18n.tsx | 6 +++++-
.../src/client-javascript/react/index.tsx | 2 +-
javascript-modules-library/src/components/render/Island.tsx | 2 +-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/jahia-test-module/src/react/server/views/testI18n/TestI18n.tsx b/jahia-test-module/src/react/server/views/testI18n/TestI18n.tsx
index 0e3fbeff..43e3ad41 100644
--- a/jahia-test-module/src/react/server/views/testI18n/TestI18n.tsx
+++ b/jahia-test-module/src/react/server/views/testI18n/TestI18n.tsx
@@ -22,7 +22,11 @@ jahiaComponent(
Test i18n: rendered client side
-
+
getSiteLocales()
diff --git a/javascript-modules-engine/src/client-javascript/react/index.tsx b/javascript-modules-engine/src/client-javascript/react/index.tsx
index ecc35d7d..f646a335 100644
--- a/javascript-modules-engine/src/client-javascript/react/index.tsx
+++ b/javascript-modules-engine/src/client-javascript/react/index.tsx
@@ -45,7 +45,7 @@ const load = async (element: HTMLElement) => {
const rawProps = element.querySelector("script[type='application/json']")?.textContent;
const props = rawProps ? devalue.parse(rawProps) : {};
- const hydrate = Boolean(element.dataset.clientOnly);
+ const hydrate = !element.dataset.clientOnly;
const { default: Component } = await import(entry);
diff --git a/javascript-modules-library/src/components/render/Island.tsx b/javascript-modules-library/src/components/render/Island.tsx
index 901e00f4..7f001a27 100644
--- a/javascript-modules-library/src/components/render/Island.tsx
+++ b/javascript-modules-library/src/components/render/Island.tsx
@@ -197,7 +197,7 @@ export function Island({
// to prevent a broken DOM structure in the browser. (e.g. a `
` inside a `
`)
createElement("jsm-island", {
"style": { display: "contents" },
- "data-client-only": clientOnly ? 1 : undefined,
+ "data-client-only": clientOnly ? true : undefined,
"data-src": entry,
"data-lang": language,
"data-bundle": bundleKey,
From d28a24b8767b232dd9c979568104a6495fa23e00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gautier=20Ben=20A=C3=AFm?=
Date: Thu, 7 Aug 2025 13:24:40 +0200
Subject: [PATCH 04/15] lint
---
.../src/components/render/Island.tsx | 154 +++++++++---------
1 file changed, 80 insertions(+), 74 deletions(-)
diff --git a/javascript-modules-library/src/components/render/Island.tsx b/javascript-modules-library/src/components/render/Island.tsx
index 7f001a27..d3489d2a 100644
--- a/javascript-modules-library/src/components/render/Island.tsx
+++ b/javascript-modules-library/src/components/render/Island.tsx
@@ -1,6 +1,6 @@
import * as devalue from "devalue";
import i18n from "i18next";
-import { createElement, type ComponentType, type ReactNode, type JSX } from "react";
+import { createElement, type ComponentType, type ReactNode } from "react";
import { I18nextProvider } from "react-i18next";
import sharedLibFiles from "virtual:shared-lib-files";
import { useServerContext } from "../../hooks/useServerContext.js";
@@ -27,78 +27,80 @@ import { AddResources } from "../AddResources.js";
* case, children are used as a placeholder until the component is hydrated.
*/
// @ts-expect-error TS complains that the signature does not match the implementation, but it does
-export function Island({}: {
- /** The React component to render. */
- component: ComponentType;
-} & (keyof Omit extends never
- ? {
- props?: never; // If the component has no properties (other than children), none can be passed
- }
- : Omit extends Required>
+export function Island(
+ props: {
+ /** The React component to render. */
+ component: ComponentType;
+ } & (keyof Omit extends never
? {
- // If at least one property of component are mandatory, they must be passed
- /** Props to forward to the component. */
- props: Omit;
+ props?: never; // If the component has no properties (other than children), none can be passed
}
- : {
- // If all properties of component are optional, they may be passed or not
- /** Props to forward to the component. */
- props?: Omit;
- }) &
- (Props extends { children: infer Children }
- ? // If the component has mandatory children, it cannot be client-only
- {
- /**
- * If false or undefined, the component will be rendered on the server. If true, server-side
- * rendering will be skipped.
- */
- clientOnly?: false;
- /** The children to render inside the component. */
- children: Children;
- }
- : Props extends { children?: infer Children }
- ? // If the component has optional children, it may be client-only or not
- | {
- // In SSR mode, the children are passed to the component and must be of the correct type
- /**
- * If false or undefined, the component will be rendered on the server. If true,
- * server-side rendering will be skipped.
- */
- clientOnly?: false;
- /** The children to render inside the component. */
- children?: Children;
- }
+ : Omit extends Required>
+ ? {
+ // If at least one property of component are mandatory, they must be passed
+ /** Props to forward to the component. */
+ props: Omit;
+ }
+ : {
+ // If all properties of component are optional, they may be passed or not
+ /** Props to forward to the component. */
+ props?: Omit;
+ }) &
+ (Props extends { children: infer Children }
+ ? // If the component has mandatory children, it cannot be client-only
+ {
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly?: false;
+ /** The children to render inside the component. */
+ children: Children;
+ }
+ : Props extends { children?: infer Children }
+ ? // If the component has optional children, it may be client-only or not
| {
- // In CSR mode, the children are used as a placeholder and may be of any type
- /**
- * If false or undefined, the component will be rendered on the server. If true,
- * server-side rendering will be skipped.
- */
- clientOnly: true;
- /** Placeholder content until the component is rendered on the client. */
- children?: ReactNode;
- }
- : // If the component has no children, it may be client-only or not
- | {
- // In SSR mode, the component cannot have children
- /**
- * If false or undefined, the component will be rendered on the server. If true,
- * server-side rendering will be skipped.
- */
- clientOnly?: false;
- // Prevent children from being passed to the component
- children?: never;
- }
+ // In SSR mode, the children are passed to the component and must be of the correct type
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly?: false;
+ /** The children to render inside the component. */
+ children?: Children;
+ }
+ | {
+ // In CSR mode, the children are used as a placeholder and may be of any type
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly: true;
+ /** Placeholder content until the component is rendered on the client. */
+ children?: ReactNode;
+ }
+ : // If the component has no children, it may be client-only or not
| {
- // In CSR mode, the children are used as a placeholder and may be of any type
- /**
- * If false or undefined, the component will be rendered on the server. If true,
- * server-side rendering will be skipped.
- */
- clientOnly: true;
- /** Placeholder content until the component is rendered on the client. */
- children?: ReactNode;
- })): ReactNode;
+ // In SSR mode, the component cannot have children
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly?: false;
+ // Prevent children from being passed to the component
+ children?: never;
+ }
+ | {
+ // In CSR mode, the children are used as a placeholder and may be of any type
+ /**
+ * If false or undefined, the component will be rendered on the server. If true,
+ * server-side rendering will be skipped.
+ */
+ clientOnly: true;
+ /** Placeholder content until the component is rendered on the client. */
+ children?: ReactNode;
+ }),
+): ReactNode;
// We use an overload rather than a single function because some props (e.g. children) are not always defined
export function Island({
@@ -107,8 +109,8 @@ export function Island({
clientOnly,
children,
}: Readonly<{
- component: ComponentType;
- props?: any;
+ component: ComponentType<{ children?: ReactNode }>;
+ props?: Record;
clientOnly?: boolean;
children?: ReactNode;
}>): ReactNode {
@@ -209,9 +211,13 @@ export function Island({
children
) : (
-
- {createElement("jsm-children", { style: { display: "contents" }, children })}
-
+
),
],
From 414bfdd2125a54ed5dd8ef6e32d6a668be9ff228 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gautier=20Ben=20A=C3=AFm?=
Date: Fri, 8 Aug 2025 10:03:22 +0200
Subject: [PATCH 05/15] fixes
---
.../1-building-a-feedback-form/README.md | 8 +--
.../src/components/render/Island.tsx | 49 ++++++++++---------
.../HelloWorld/Celebrate.client.tsx | 4 +-
.../components/HelloWorld/default.server.tsx | 4 +-
4 files changed, 33 insertions(+), 32 deletions(-)
diff --git a/docs/2-guides/1-building-a-feedback-form/README.md b/docs/2-guides/1-building-a-feedback-form/README.md
index 3f12802c..17168ea0 100644
--- a/docs/2-guides/1-building-a-feedback-form/README.md
+++ b/docs/2-guides/1-building-a-feedback-form/README.md
@@ -81,17 +81,17 @@ jahiaComponent(
nodeType: "hydrogen:feedbackWidget",
componentType: "view",
},
- ({ question }: Props) => ,
+ ({ question }: Props) => ,
);
```
You'll need to restart `yarn dev` for Vite to collect your new client files, but once pushed, should see the exact same button as before, but now it will alert "Hello World!" when clicked.
-The `RenderInBrowser` component is a wrapper that will ensure the code of `Widget` gets forwarded to the browser, enabling what is called Island Architecture: this component will be rendered client-side, but the rest of the page will remain server-rendered. This is a great way to improve performance, as it allows to only ship the JavaScript that is needed for the interactive parts of your page.
+The `Island` component is a wrapper that will ensure the code of `Widget` gets forwarded to the browser, enabling what is called Island Architecture: this component will be rendered client-side, but the rest of the page will remain server-rendered. This is a great way to improve performance, as it allows to only ship the JavaScript that is needed for the interactive parts of your page.
-The `props` prop of `RenderInBrowser` allows you to pass props to the component that will be rendered in the browser. Because they will be sent to the browser, they should be serializable.
+The `props` prop of `Island` allows you to pass props to the component that will be rendered in the browser. Because they will be sent to the browser, they should be serializable.
-If you nest children to `RenderInBrowser`, they will be displayed until `Widget` is loaded. This is where you can put a loading message for instance: `The widget is loading...`.
+If you nest children to `Island` in `clientOnly` mode, they will be displayed until `Widget` is loaded. This is where you can put a loading message for instance: `The widget is loading...`.
You now have all the tools needed to build any client-side component, but keep on reading to learn how to send data to jCustomer.
diff --git a/javascript-modules-library/src/components/render/Island.tsx b/javascript-modules-library/src/components/render/Island.tsx
index d3489d2a..38c459ce 100644
--- a/javascript-modules-library/src/components/render/Island.tsx
+++ b/javascript-modules-library/src/components/render/Island.tsx
@@ -33,18 +33,20 @@ export function Island(
component: ComponentType;
} & (keyof Omit extends never
? {
- props?: never; // If the component has no properties (other than children), none can be passed
+ // If the component has no properties (other than children), none can be passed
+ props?: never;
}
- : Omit extends Required>
+ : Partial> extends Omit
? {
- // If at least one property of component are mandatory, they must be passed
+ // If all properties of component are optional, they may be passed or not,
+ // props will default to an empty object if omitted
/** Props to forward to the component. */
- props: Omit;
+ props?: Omit;
}
: {
- // If all properties of component are optional, they may be passed or not
+ // If at least one property is required, providing props is mandatory
/** Props to forward to the component. */
- props?: Omit;
+ props: Omit;
}) &
(Props extends { children: infer Children }
? // If the component has mandatory children, it cannot be client-only
@@ -57,7 +59,7 @@ export function Island(
/** The children to render inside the component. */
children: Children;
}
- : Props extends { children?: infer Children }
+ : "children" extends keyof Props
? // If the component has optional children, it may be client-only or not
| {
// In SSR mode, the children are passed to the component and must be of the correct type
@@ -67,7 +69,7 @@ export function Island(
*/
clientOnly?: false;
/** The children to render inside the component. */
- children?: Children;
+ children?: Props["children"];
}
| {
// In CSR mode, the children are used as a placeholder and may be of any type
@@ -134,25 +136,17 @@ export function Island({
<>
``)
- .join("")
- }
- `
+
+ ${
+ /**
+ * Module preload hints for shared libraries, deep in the dependency tree.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/modulepreload
+ */
+ sharedLibFiles
+ .map((file) => ``)
+ .join("")
+ }`
}
/>
`}
/>
{i18nResourceBundle && (
@@ -189,7 +191,6 @@ export function Island({
)}
`}
/>
diff --git a/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx b/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
index d6aad4b3..224dc7fc 100644
--- a/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
+++ b/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
@@ -1,9 +1,9 @@
import clsx from "clsx";
import { t } from "i18next";
-import { useEffect, useState } from "react";
+import { useEffect, useState, type ReactNode } from "react";
import classes from "./component.module.css";
-export default function () {
+export default function (props: { children?: string }) {
const [confetti, setConfetti] = useState();
useEffect(() => {
diff --git a/samples/hydrogen/src/components/HelloWorld/default.server.tsx b/samples/hydrogen/src/components/HelloWorld/default.server.tsx
index 29b4e23f..e470a34e 100644
--- a/samples/hydrogen/src/components/HelloWorld/default.server.tsx
+++ b/samples/hydrogen/src/components/HelloWorld/default.server.tsx
@@ -1,5 +1,5 @@
import {
- HydrateInBrowser,
+ Island,
RenderChildren,
buildModuleFileUrl,
jahiaComponent,
@@ -51,7 +51,7 @@ jahiaComponent(
/>
{t("OfBsezopuIko8aJ6X3kpw")}
-
+
Date: Fri, 8 Aug 2025 10:04:50 +0200
Subject: [PATCH 06/15] Apply suggestion from @GauBen
---
.../hydrogen/src/components/HelloWorld/Celebrate.client.tsx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx b/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
index 224dc7fc..dc3c838c 100644
--- a/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
+++ b/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
@@ -2,7 +2,10 @@ import clsx from "clsx";
import { t } from "i18next";
import { useEffect, useState, type ReactNode } from "react";
import classes from "./component.module.css";
+import { useEffect, useState } from "react";
+import classes from "./component.module.css";
+export default function () {
export default function (props: { children?: string }) {
const [confetti, setConfetti] = useState();
From 3112b6076252b9598754e03e4b77e5d63def846b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gautier=20Ben=20A=C3=AFm?=
Date: Fri, 8 Aug 2025 10:06:32 +0200
Subject: [PATCH 07/15] ???
---
.../hydrogen/src/components/HelloWorld/Celebrate.client.tsx | 3 ---
1 file changed, 3 deletions(-)
diff --git a/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx b/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
index dc3c838c..d6aad4b3 100644
--- a/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
+++ b/samples/hydrogen/src/components/HelloWorld/Celebrate.client.tsx
@@ -1,12 +1,9 @@
import clsx from "clsx";
import { t } from "i18next";
-import { useEffect, useState, type ReactNode } from "react";
-import classes from "./component.module.css";
import { useEffect, useState } from "react";
import classes from "./component.module.css";
export default function () {
-export default function (props: { children?: string }) {
const [confetti, setConfetti] = useState();
useEffect(() => {
From 3e26c1bae11c8e225346608e27aad7d557313d4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gautier=20Ben=20A=C3=AFm?=
Date: Fri, 8 Aug 2025 16:59:15 +0200
Subject: [PATCH 08/15] doc
---
.../2-accessibility-and-performance/README.md | 0
docs/2-guides/2-island-architecture/README.md | 293 ++++++++++++++++++
.../2-island-architecture/accordion.svg | 1 +
.../2-island-architecture/islands.svg | 1 +
.../3-building-a-menu-and-sitemap/README.md | 0
docs/2-guides/4-adding-icons/README.md | 0
docs/2-guides/5-adding-tailwind/README.md | 0
.../6-building-for-production/README.md | 0
docs/2-guides/7-rendering-markdown/README.md | 0
docs/2-guides/8-rss-feed/README.md | 0
docs/2-guides/9-debugging/README.md | 0
.../2-guides/A-using-web-components/README.md | 0
docs/2-guides/README.md | 10 +-
docs/3-reference/2-tools/README.md | 0
docs/3-reference/3-jcr/README.md | 0
docs/3-reference/4-mixins/README.md | 0
.../3-reference/5-children-and-area/README.md | 0
docs/3-reference/6-nav-builder/README.md | 0
docs/3-reference/7-ui-extensions/README.md | 0
.../README.md | 0
.../9-java-interoperability/README.md | 0
.../3-reference/A-ssr-and-hydration/README.md | 0
docs/README.md | 1 +
.../templates/module/vite.config.mjs | 2 +-
vite-plugin/src/index.ts | 2 +-
25 files changed, 299 insertions(+), 11 deletions(-)
delete mode 100644 docs/2-guides/2-accessibility-and-performance/README.md
create mode 100644 docs/2-guides/2-island-architecture/README.md
create mode 100644 docs/2-guides/2-island-architecture/accordion.svg
create mode 100644 docs/2-guides/2-island-architecture/islands.svg
delete mode 100644 docs/2-guides/3-building-a-menu-and-sitemap/README.md
delete mode 100644 docs/2-guides/4-adding-icons/README.md
delete mode 100644 docs/2-guides/5-adding-tailwind/README.md
delete mode 100644 docs/2-guides/6-building-for-production/README.md
delete mode 100644 docs/2-guides/7-rendering-markdown/README.md
delete mode 100644 docs/2-guides/8-rss-feed/README.md
delete mode 100644 docs/2-guides/9-debugging/README.md
delete mode 100644 docs/2-guides/A-using-web-components/README.md
delete mode 100644 docs/3-reference/2-tools/README.md
delete mode 100644 docs/3-reference/3-jcr/README.md
delete mode 100644 docs/3-reference/4-mixins/README.md
delete mode 100644 docs/3-reference/5-children-and-area/README.md
delete mode 100644 docs/3-reference/6-nav-builder/README.md
delete mode 100644 docs/3-reference/7-ui-extensions/README.md
delete mode 100644 docs/3-reference/8-single-directory-components-and-resources/README.md
delete mode 100644 docs/3-reference/9-java-interoperability/README.md
delete mode 100644 docs/3-reference/A-ssr-and-hydration/README.md
diff --git a/docs/2-guides/2-accessibility-and-performance/README.md b/docs/2-guides/2-accessibility-and-performance/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/2-island-architecture/README.md b/docs/2-guides/2-island-architecture/README.md
new file mode 100644
index 00000000..4407817f
--- /dev/null
+++ b/docs/2-guides/2-island-architecture/README.md
@@ -0,0 +1,293 @@
+# Island Architecture
+
+Jahia JavaScript Modules offers a first-class support for this architectural pattern, allowing interactivity without compromising on performance.
+
+## What is Island Architecture?
+
+We have written a [complete article on the topic,](https://www.jahia.com/blog/leveraging-the-island-architecture-in-jahia-cms) but here is a quick summary:
+
+- Instead of shipping fully static or fully dynamic pages, Island Architecture is the middle ground where most of the page is static, but specific parts are made interactive on page load.
+
+ [A page mockup with two interactive islands: a navigation bar and a video player](./islands.svg)
+
+ In this example, the page is mostly static, with the exception of the `` and `` components, which are the islands of interactivity of the page. After the initial page load, JavaScript is used to make them interactive without affecting the rest of the page.
+
+- Island Architecture offers the performance and SEO benefits of server-side rendering, but makes it easy to create highly interactive user experiences.
+
+- The difference between islands and using server-side rendering with a bit of jQuery is that, when building islands, the exact same React components run on the server and the client. Having a single-language codebase is easier to maintain in the long run.
+
+## The `` component
+
+The `` component is the base of the Island Architecture in Jahia. It can be imported from the `@jahia/javascript-modules-library` and used in any React view or template:
+
+```tsx
+import { Island } from "@jahia/javascript-modules-library";
+```
+
+As with all imports from `@jahia/javascript-modules-library`, the `` component **can only be used on the server.**
+
+Server files, files in `.server.tsx`, are used as entry points for your server code. They contain views and templates to be registered by Jahia.
+
+Client files, files in `.client.tsx`, are used as entry points for your client code. All client files, as well as all their imports, will be made available for the browser to download. They should not contain any sensitive information, and cannot import server APIs (`@jahia/javascript-modules-library` and `.server.tsx` files).
+
+In client files, **only the default export** can be used to create an island. For instance, here is a minimal interactive button:
+
+```tsx
+// Button.client.tsx
+export default function Button() {
+ return (
+
+ );
+}
+```
+
+If you use this button directly in a server file, it will not work as you expect:
+
+```tsx
+// default.server.tsx
+import { jahiaComponent } from "@jahia/javascript-modules-library";
+import Button from "./Button.client.tsx";
+
+jahiaComponent(
+ {
+ componentType: "view",
+ nodeType: "hydrogen:example",
+ },
+ () => (
+
+
Hello World
+
+ {/* ❌ Do not do that, it does not work */}
+
+
+
+ ),
+);
+```
+
+Your button will be sent properly to the client, but **not made interactive.** This is because the default rendering mode of JavaScript Modules (and Jahia in general) is server-side rendering. **No JS is sent to the browser by default,** and therefore your button doesn't get its event listener attached.
+
+The solution is the `` component:
+
+```tsx
+// default.server.tsx
+import { Island, jahiaComponent } from "@jahia/javascript-modules-library";
+import Button from "./Button.client.tsx";
+
+jahiaComponent(
+ {
+ componentType: "view",
+ nodeType: "hydrogen:example",
+ },
+ () => (
+
+
Hello World
+
+ {/* ✅ This works*/}
+
+
+
+ ),
+);
+```
+
+The `` component always takes a `component` prop, which is the React component to be rendered as an island. It must be the default export from a `.client.tsx` file, otherwise it will not work.
+
+It can also take other props, which are detailed in the following sections.
+
+## `clientOnly`
+
+By default, all islands are rendered on the server and made interactive on the client (the process is called [hydration](https://18.react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html)). This is great for the perceived performance of your application because even before being interactive, your page can be read by the user.
+
+Sometimes, the server cannot render the content (for instance, because it needs browser APIs like `window`, `document` or `navigator.geolocation`). For these cases, the `` component has `clientOnly` code, which will skip server-side rendering and only render your component on the client.
+
+```tsx
+// Language.client.tsx
+export default function Language() {
+ // The `navigator.language` API is only available in the browser,
+ // and would error on the server as being undefined
+ return
According to your browser, you speak {navigator.language}.
;
+}
+```
+
+```tsx
+// default.server.tsx
+import { Island, jahiaComponent } from "@jahia/javascript-modules-library";
+import Language from "./Language.client.tsx";
+
+jahiaComponent(
+ {
+ componentType: "view",
+ nodeType: "hydrogen:example",
+ },
+ () => (
+
+
+
+ ),
+);
+```
+
+## `props`
+
+Your island component, as any React component, may take props. To do so, pass all the props of your component through the `props` prop of ``.
+
+Because these props will be sent to the browser, two constraints apply:
+
+- **Do not pass any sensitive information,** such as API keys.
+- **The props must be serializable,** which means only a subset of all JS objects can be used. The serialization is performed by [devalue](https://www.npmjs.com/package/devalue), which offers a wider range of supported types than `JSON.stringify`, but still has limitations. For instance, you cannot send a JCR node through the props of a client component.
+
+Here is an example of what you can do:
+
+```tsx
+// Pizza.client.tsx
+export default function Pizza({
+ toppings,
+ selection,
+}: {
+ /** All available pizza ingredients */
+ toppings: string[];
+ /** The user's current selection */
+ selection: Set;
+}) {
+ return (
+ // You can use React fragments, your island does not have to be a single element
+ <>
+
+ >
+ );
+}
+```
+
+```tsx
+// default.server.tsx
+import { Island, jahiaComponent } from "@jahia/javascript-modules-library";
+import Pizza from "./Pizza.client.tsx";
+
+jahiaComponent(
+ {
+ componentType: "view",
+ nodeType: "hydrogen:example",
+ },
+ () => (
+
+
+
+ ),
+);
+```
+
+## `children`
+
+Last but not least, the `children` prop, which is the technical name for all children passed to a React component. (`} />` is the same as ``.)
+
+The `` component can take children, but its behavior depends on its `clientOnly` prop.
+
+In default mode (without `clientOnly`), the children are rendered on the server and sent to the client, as children of your island component. The children will not be made interactive.
+
+This behavior enables the development of components like accordions, where the `` is not a leaf of the component tree.
+
+[Schema of the accordion component](./accordion.svg)
+
+Such a component can be implemented as follows:
+
+```tsx
+// Accordion.client.tsx
+import type { ReactNode } from "react";
+
+export default function Accordion({ children }: { children: ReactNode }) {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+
+ {/* Children will be inserted here: */}
+ {children}
+
+
+ );
+}
+```
+
+And on the server:
+
+```tsx
+// default.server.tsx
+import { Island, jahiaComponent } from "@jahia/javascript-modules-library";
+import Accordion from "./Accordion.client.tsx";
+
+jahiaComponent(
+ {
+ componentType: "view",
+ nodeType: "hydrogen:example",
+ },
+ () => (
+
+
+
I'm rendered on the server!
+
+
+ ),
+);
+```
+
+A few things to note:
+
+- The `{children}` insertion point must always be there. If you want to hide the children of your component, use CSS instead of a JS condition. Otherwise, they might not be sent to the client and your component will appear to have no children.
+- Children will be wrapped in a `jsm-children` element. This should not change anything 99% of the time, but don't use the `>` CSS selector to target children of your component.
+
+In `clientOnly` mode, the children of an island will not be used as children of your island component. Instead, they will be rendered on the server and used as a placeholder until the client component is loaded.
+
+```tsx
+// default.server.tsx
+import { Island, jahiaComponent } from "@jahia/javascript-modules-library";
+import Map from "./Map.client.tsx";
+
+jahiaComponent(
+ {
+ componentType: "view",
+ nodeType: "hydrogen:example",
+ },
+ () => (
+
+
+ {/* Placeholder until has loaded */}
+
The map is loading...
+
+
+ ),
+);
+```
+
+## Implementation details
+
+It is not necessary to know any of this to create a successful Jahia integration, but it might come in handy if you need to debug your application.
+
+- The `` component will be sent to the client as a `` custom element. In client only mode, it will have a `data-client-only` attribute. Do not target `jsm-island` nor `jsm-children` in your CSS as they are implementation details and may change in non-major versions.
+
+- We have written a complete article on the implement details of the `` component. You can read it on our blog: [Under the Hood: Hydrating React Components in Java](https://www.jahia.com/blog/under-the-hood-hydrating-react-components-in-java).
diff --git a/docs/2-guides/2-island-architecture/accordion.svg b/docs/2-guides/2-island-architecture/accordion.svg
new file mode 100644
index 00000000..2f978119
--- /dev/null
+++ b/docs/2-guides/2-island-architecture/accordion.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/2-guides/2-island-architecture/islands.svg b/docs/2-guides/2-island-architecture/islands.svg
new file mode 100644
index 00000000..b9084c8a
--- /dev/null
+++ b/docs/2-guides/2-island-architecture/islands.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/2-guides/3-building-a-menu-and-sitemap/README.md b/docs/2-guides/3-building-a-menu-and-sitemap/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/4-adding-icons/README.md b/docs/2-guides/4-adding-icons/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/5-adding-tailwind/README.md b/docs/2-guides/5-adding-tailwind/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/6-building-for-production/README.md b/docs/2-guides/6-building-for-production/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/7-rendering-markdown/README.md b/docs/2-guides/7-rendering-markdown/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/8-rss-feed/README.md b/docs/2-guides/8-rss-feed/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/9-debugging/README.md b/docs/2-guides/9-debugging/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/A-using-web-components/README.md b/docs/2-guides/A-using-web-components/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/2-guides/README.md b/docs/2-guides/README.md
index 900c96c0..fdd0aac9 100644
--- a/docs/2-guides/README.md
+++ b/docs/2-guides/README.md
@@ -3,15 +3,7 @@
This section contains independent, self-contained guides that help you build specific features or solve specific problems. Each guide is a step-by-step tutorial that you can follow to achieve a specific goal.
- [Building a Feedback Form](./1-building-a-feedback-form/)
-- [Accessibility and Performance](./2-accessibility-and-performance/)
-- [Building a Menu and Sitemap](./3-building-a-menu-and-sitemap/)
-- [Adding Icons](./4-adding-icons/)
-- [Adding Tailwind](./5-adding-tailwind/)
-- [Building for Production](./6-building-for-production/)
-- [Rendering Markdown](./7-rendering-markdown/)
-- [RSS Feed](./8-rss-feed/)
-- [Debugging](./9-debugging/)
-- [Using Web Components](./A-using-web-components/)
+- [Island Architecture](./2-island-architecture/)
More guides are coming soon, and contributions are welcome!
diff --git a/docs/3-reference/2-tools/README.md b/docs/3-reference/2-tools/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/3-jcr/README.md b/docs/3-reference/3-jcr/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/4-mixins/README.md b/docs/3-reference/4-mixins/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/5-children-and-area/README.md b/docs/3-reference/5-children-and-area/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/6-nav-builder/README.md b/docs/3-reference/6-nav-builder/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/7-ui-extensions/README.md b/docs/3-reference/7-ui-extensions/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/8-single-directory-components-and-resources/README.md b/docs/3-reference/8-single-directory-components-and-resources/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/9-java-interoperability/README.md b/docs/3-reference/9-java-interoperability/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/3-reference/A-ssr-and-hydration/README.md b/docs/3-reference/A-ssr-and-hydration/README.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/README.md b/docs/README.md
index 03e156c0..73dcce36 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -25,6 +25,7 @@ Start here: [Setting up your dev environment](./1-getting-started/1-dev-environm
This section contains independent, self-contained guides that help you build specific features or solve specific problems. Each guide is a step-by-step tutorial that you can follow to achieve a specific goal.
- [Building a Feedback Form](./2-guides/1-building-a-feedback-form/)
+- [Island Architecture](./2-guides/2-island-architecture/)