logo ce-autoloader

Load web components on demand
if and when they're needed.

About

You need a custom <select> menu. Suddenly, you've installed 500MB of node_modules, wrapped your site inside a russian doll of <App><Provider><Router tags, and then wait for 1MB of JavaScript to run before the user can even scroll. 😖

When you try to fix it with SSR, you're now married to the framework and the "Hydration Mismatch" horror movie — where the server and client start arguing about what a Date object looks like. Now you're paying for a beefy server just to stringify JSON into HTML, while losing your mind over cache-control headers that are never quite right. Content is stale, CPU is 100%, and your wallet is crying.

I just want static pages with a few interactive components.
If a component isn’t used on the page, it shouldn’t be downloaded.
The fact that this is still hard is kind of absurd.

ce-autoloader is a lightweight library born out of this frustration. It brings Islands Architecture to any site, with any framework.

The special sauce? A shared component catalog, lazy-loading components by default and CSS Animation/View Transitions API to animate them into place.

Fast sites, minimal JavaScript, without over-engineering. 😮‍💨

Get started

It's just 3 steps. No build tools required!

Install via npm or CDN
npm install ce-autoloader
Define your catalog
import CERegistry from 'ce-autoloader';

const registry = new CERegistry({
	catalog: {
		'my-component': () => import('./my-component.js')
	}
});
Add <my-component> on the body HTML
<my-component loading="visible">World</my-component>

Documentation

Universal

Built to load any standard Web Component, regardless of origin.


Because ce-autoloader targets the standard Custom Elements API, it doesn't care how your component was built. You can mix and match components from React, Vue, Svelte, Lit, or Vanilla JS in the same project, without a shared build step.

The examples below are three separate components, each using a different framework's internal logic and state management, but all orchestrated by a single instance of ce-autoloader.

Loading Strategies

Fine-tune component loading for every unique use case.


The loading attribute defines the strategy for when a component is loaded. By default, components use the visible strategy to maximize initial performance.

Strategy Mechanism Description
visible (Default) Intersection Observer Components are downloaded and defined only when they enter the viewport. Perfect for content "below the fold" to keep initial page weight low.
click onClick or onTouch The module is fetched when a user clicks or touches the element. Ideal for heavy UI widgets like maps, complex editors, or modal content.
eager Immediate activation Downloads the module as soon as it's found in the DOM. Use this for critical "above the fold" components like navigation headers or hero sections.
*custom* Manual/Programmatic mode You can specify any custom string (e.g., loading="lazy-load") and trigger it manually via registry.upgrade('lazy-load').
Visible by default Click to load * custom (click) *

Custom Loaders

Tailor the loader to your specific workflow.


Don't want to register 250 components one by one? we got you. Wildcard Loaders allow you to customize the loader to fit your needs.

In this example, we registered a loader for nord-*. The browser sees <nord-qrcode>, and our loader is called. It fetches the Correct module from the esm CDN.

/**
 * Custom loader for the Nord Health components
 */
"nord-*": async (full_name) => {
    // 1. Extract the specific component name (e.g., 'qrcode' from 'nord-qrcode')
    const [, namespace, name] = full_name.match(/^([a-z]+)-(.*)/);

    // 2. Resolve the module URL (dynamic import)
    const module = await import(/* @vite-ignore */ `https://esm.sh/@nordhealth/components/lib/${capitalize(name)}.js`);

    // 3. Define the component if not already registered
    if (!customElements.get(full_name)) {
        customElements.define(full_name, module.default);
    }
    return module
},

Now, every time the browser sees a tag with prefix nord tag, it will automatically load the component from the esm CDN.

<nord-qrcode>
<nord-select>
<nord-date-picker>
Open menu (⌘+K)
<nord-command-menu>
Canine Feline Rodents
<nord-tag-group>

Animations & Transitions

Define exactly how and when components surface.

Web components should feel like a native part of the page, not an afterthought. Orchestrate how elements surface with coordinated entry patterns that eliminate layout shift and create a fluid, intentional experience from the first frame.

Every component moves through two states not-defined → defined, that you can style using CSS selectors:

:not(:defined)
Placeholder State.

The custom element is in the DOM but the browser doesn't know its behavior yet. Use this to set min-height or aspect-ratio to reserve space on the page.

A forever not-loaded element
:not(:defined)[ce="loading"]
Loading State.

Triggered when the module is being loaded. This is the perfect time to show a Skeleton Loader or a progress indicator.

A loading element
ce="defined"
Interactive State.

The component is defined and upgraded. Use this to trigger a final "fade-in" or entrance animation.

A interactive element

The CSS view transitions are supported out of the box, and you can name them using view-transition-name attribute.

This allows you to create seamless transitions between states, and make them smoothly animate to it's place

🌜 (click to load) 🌛

Native Telemetry & Insights

Performance as a Standard.

Stop guessing how your islands perform. With telemetry baked into the core, you get deep visibility into load times and execution overhead using native browser APIs—keeping your overhead near zero while your insights stay sharp.

High-resolution markers

Granular metrics for every stage of the lifecycle, visible in the Performance Tab.

  • load:tag-name: The time it took to fetch the module from network.
  • define:tag-name: The time it took to register the component.
  • transition: The time it took to transition the component.

Error Fallbacks & Resilience

Built for the Real-World Web.


The network is unreliable, but your UI shouldn't be.

Define a custom fallback component via fallback that is displayed when a module throws an error during loading.

Configuration

When creating a new CERegistry, you can pass an options object to customize its behavior.

Option Type Default Description
catalog Record<string, string | function> Required A mapping of tag names to their respective loader functions or URLs.
root HTMLElement document.body The root element where the loader will search for custom elements.
live boolean true Automatically watches the DOM for new custom elements to upgrade.
transition boolean true Enables the View Transitions API for smooth element upgrades.
directives string[] ['eager', 'visible', 'click'] A list of supported loading triggers.
defaultDirective string visible The default loading strategy, when it's not specified on the element.
fallback CustomElementConstructor undefined A custom element class to use if a component fails to load.

FAQ (Perguntas Frequentes)

Why web components?

Web components with light-dom&css variables are the GOAT! They are the only way to reuse components across frameworks, and since they're part of HTML spec, they'll still work in 20 years.

Aren't you tired of recreating the same components over and over again every 5 years with the new shiny framework? Let's stop this madness!

⚛️ How to use this with react/vue/svelte/etç?

Vue and Svelte already export web-components out of the box!

Only React is purposefully missing this feature, so you need to use a library like remount or react-to-web-component.

import r2wc from "@r2wc/react-to-web-component"

const Greeter = ({ name }) => {
	return <div>Hello, {name}!</div>
}

export default r2wc(Greeter, {
	props: {
		name: "string",
	}
});

					

🚀 Wait, isn't this like Astro?

Yes and No. Both use the "Island Architecture" concept to reduce JS bloat, but they are :

  • Astro (Server-First): Renders at build time or at server. Great for new apps.
  • ce-autoloader (Client-First): Runs at runtime. Great for adding islands to existing Python, Ruby, Elixir or static HTML pages.

🚀 What's the performance like?

Glad you asked! Core Web Vitals with ce-autoloader matches or exceeds SSR frameworks like Next.js. Don't believe me? This is the score of the page you're reading now:

Core Web Vitals