Sapling Islands

The sapling-island web component is a lightweight solution for implementing islands architecture in your Sapling applications. It allows you to progressively hydrate parts of your page, loading JavaScript and styles only when needed.

Installation

You can enable Sapling Islands in two ways:

The simplest way is to enable islands in your Layout configuration:

export default async function Layout(props: LayoutProps) {
  return await SaplingLayout({
    enableIslands: true,
    // ... other layout options
  });
}

This will automatically add the necessary script and CSS to your page.

2. Manual Installation

Alternatively, you can manually add the required resources. Add the Sapling Islands script to your page via CDN:

<script type="module" src="https://sapling-is.land"></script>

Add the following CSS to ensure proper rendering of the content inside the islands:

sapling-island {
  display: contents;
}

There may be some cases where you don't want display: contents; however, in most cases it's a good idea to use it.

Overview

Islands architecture is a pattern where most of your page remains static HTML, with interactive "islands" that get hydrated with JavaScript when needed. This approach can significantly improve initial page load performance by deferring the loading of non-critical JavaScript.

Basic Usage

Wrap any content that requires JavaScript or additional styles in a sapling-island component:

<sapling-island>
  <template>
    <script src="/scripts/interactive.js" type="module"></script>
    <style>
      /* Styles that are only needed for this interactive component */
    </style>
  </template>
  <div>Your interactive content here</div>
</sapling-island>

Loading Strategies

The component supports several loading strategies through the loading attribute:

Load (Default)

If no loading attribute is specified, or when set to "load", the island loads immediately when the page loads:

<!-- Without loading attribute -->
<sapling-island>
  <template>
    <script src="/scripts/immediate.js" type="module"></script>
  </template>
</sapling-island>

<!-- Explicitly set to load -->
<sapling-island loading="load">
  <template>
    <script src="/scripts/immediate.js" type="module"></script>
  </template>
</sapling-island>

Visible

Loads when the component becomes visible in the viewport:

<sapling-island loading="visible">
  <template>
    <script src="/scripts/lazy.js" type="module"></script>
  </template>
</sapling-island>

Idle

Loads when the browser is idle:

<sapling-island loading="idle">
  <template>
    <script src="/scripts/low-priority.js" type="module"></script>
  </template>
</sapling-island>

Media Query

Loads when a media query condition is met:

<sapling-island loading="(min-width: 768px)">
  <template>
    <script src="/scripts/desktop-only.js" type="module"></script>
  </template>
</sapling-island>

Hydration State

When an island is hydrated, it receives a hydrated attribute that you can use for styling:

sapling-island[hydrated] {
  /* Styles for hydrated state */
}

Timeout Option

You can specify a timeout for loading strategies using the timeout attribute. The island will hydrate when either the loading strategy condition is met OR the timeout is reached, whichever comes first:

<sapling-island loading="visible" timeout="5000">
  <template>
    <script src="/scripts/important.js" type="module"></script>
  </template>
</sapling-island>

In the example above, the island will hydrate either when it becomes visible OR after 5 seconds, whichever happens first.

Events

The component dispatches a island:hydrated event when hydration is complete:

document.querySelector('sapling-island').addEventListener('island:hydrated', () => {
  console.log('Island has been hydrated');
});

Best Practices

  1. Template Content Only Place <script> and <style> tags inside the <template> element to prevent them from loading until needed:

    <sapling-island loading="visible">
      <template>
        <!-- Scripts and styles go here -->
        <script src="/scripts/feature.js" type="module"></script>
        <style>/* Feature styles */</style>
      </template>
      <!-- Actual content goes outside template -->
      <div class="feature">...</div>
    </sapling-island>
  2. Progressive Enhancement Design your islands to enhance existing static content rather than being required for basic functionality:

    <!-- Static content works without JavaScript -->
    <div class="content">Static content</div>
    
    <!-- Interactive features are added through islands -->
    <sapling-island loading="visible">
      <template>
        <script src="/scripts/enhance.js" type="module"></script>
      </template>
      <div class="enhancement">Interactive features</div>
    </sapling-island>
  3. Performance Considerations

    • Use visible for below-the-fold content
    • Use idle for non-critical enhancements
    • Use media queries for device-specific features
    • Set appropriate timeouts for critical features

Example: Interactive Time Display

Here's a complete example of using sapling-island for a dynamic time display:

<sapling-island loading="visible">
  <template>
    <script>
      const time = document.querySelector("time");
      setInterval(() => {
        time.textContent = new Date().toLocaleTimeString();
      }, 1000);
    </script>
  </template>
  <div class="content">
    <p>The time updates every second after hydration:</p>
    <p>Current time: <time>00:00</time></p>
  </div>
</sapling-island>

Browser Support

The sapling-island component uses standard web APIs and includes fallbacks for broader browser support:

  • Uses IntersectionObserver for visibility detection
  • Falls back to setTimeout when requestIdleCallback is not available
  • Supports all modern browsers that implement Custom Elements v1

For more examples and use cases, check out our Examples section.