Interactive React Islands with Sitecore and Astro

Anton Tishchenko
Anton Tishchenko
Cover Image for Interactive React Islands with Sitecore and Astro

Imagine, that you have decided to go with Astro for your Sitecore implementation. Your decision is based on the desire to use a content-focused framework for content-focused pages. Major pages on CMS are content-focused. The key letter “C” in “CMS” stays for “Content”. That is the way you want to achieve the best possible performance and the best development experience. But some of your pages require to be interactive. And you need to mix the static content with interactivity.

Astro has the “Islands” architecture. Astro even pioneered and popularized this frontend architecture. This architecture allows you to split your web pages into regions that are called “Islands”. And you can control the behavior of each region separately. It can be static HTML, server-rendered HTML, or interactive island.

You have 2 options for writing interactive island logic:

  • Use vanilla JavaScript(TypeScript)
  • Use the framework of your choice

If interactivity logic is simple, I suggest using only JavaScript with no additional frameworks. It allows your website to have better performance and be as clean as possible.

But if you have something of medium or higher complexity, there is no need to torture yourself with vanilla JavaScript. You may choose the framework of your choice. Astro supports React, Preact, Svelte, Vue, SolidJs, AlpineJS and Lit. And it also has community support for Angular.

As you may guessed by the article name, we will talk about React. The base installation and usage are described in the official documentation. We will do the same but with a focus on a Sitecore-based website.

Installation

  1. First of all, you will need to install dependencies
npm install @astrojs/react
npm install react react-dom
  1. Then apply integration to the astro.config.mjs file
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

export default defineConfig({
  // ...
  integrations: [react()],
  //             ^^^^^^^
});

Simple React component

Now, we can create our first component:

import React, {useState} from "react";

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div className="flex">
      <div>
        Count: {count}
      </div>
      <button onClick={() => setCount(count + 1)} className="btn btn-primary m-2">
        Increment
      </button>
      <button onClick={() => setCount(count - 1)} className="btn btn-primary m-2">
        Decrement
      </button>
    </div>
  );
};

export default Counter;

and use it inside Astro pages(or other Astro components):

---
import StyleguideSpecimen from '../styleguide/Styleguide-Specimen.astro';
import Counter from './react/Counter';

---
<StyleguideSpecimen route={Astro.props.route} e2eId="styleguide-integration-react-simple">
  <Counter client:load/>
</StyleguideSpecimen>

Astro has a few directives that allow to control island behavior:

  • client:load - the component will be loaded immediately.
  • client:idle - the component will be loaded when the browser hits the idle mode.
  • client:visible - the component will be loaded, once it is present on the screen.
  • client:media='(max-width:x)' - the component will be loaded immediately, but only if the CSS media-query is met.
  • client:only='<your-framework'> - the component will be rendered only on client side.

Using data from the Sitecore

Your React components can render Sitecore fields as before:

import React from 'react';
import { Text, RichText } from '@sitecore-jss/sitecore-jss-react';

const ContentBlock = (props) => {
  const fields = props.fields;

  return (
    <div className="contentBlock">
      <Text tag="h6" className="contentTitle" field={fields.heading} />
      <RichText className="contentDescription" field={fields.description} />
    </div>
  );
}

export default ContentBlock;

All that you need is to pass props to the component

---
import StyleguideSpecimen from "../styleguide/Styleguide-Specimen.astro";
import ContentBlock from "./react/ContentBlock";
---

<StyleguideSpecimen
  route={Astro.props.route}
  e2eId="styleguide-integration-react-sitecore-content"
>
  The same fields output but using React based component.
  <ContentBlock client:load fields={Astro.props.route.fields} />
</StyleguideSpecimen>

Placeholders in React components

And you can still use the React placeholders inside your React components. (You can use only React components inside React placeholder. Usage Astro components inside React doesn’t make a lot of sense. And it will be too crazy…)

---
import StyleguideSpecimen from "../styleguide/Styleguide-Specimen.astro";
import { Placeholder } from '@sitecore-jss/sitecore-jss-react';
import ComponentFactory from "./react/componentFactory";

---
<StyleguideSpecimen
  route={Astro.props.route}
  e2eId="styleguide-integration-react-placeholder"
>
<Placeholder
    name="react"
    rendering={Astro.props.route}
    componentFactory={ComponentFactory}
  />
</StyleguideSpecimen>

<script is:inline>
if (window.top.Sitecore != null) {
  const chromeTags = document.getElementsByTagName("code");
  for (let i = 0; i < chromeTags.length; i++) {
    const element = chromeTags[i];
    if (element.attributes.getNamedItem("phkey") !== undefined) {
      const phKey = element.attributes.getNamedItem("phkey")?.value ?? "";
      if (element.attributes.getNamedItem("key")?.value === undefined) {
        element.setAttribute("key", phKey);
      }
    }
  }
}
</script>

You need to pass the standard layout service output to the placeholder and the React components factory. The script part at the bottom is executed only in Experience editor mode and it could be added to the page only once.

With this approach, you get an Astro-based website with the ability to use React components. And the Experience Editor is fully supported for both Astro and React components.

Do you want to learn more? All code is hosted in our GitHub repository.