How to use Web Components with Next.js and TypeScript

In my livestream today I had the need to bring in a spinner component to show work in progress in my app. However found that existing React spinners were too heavy. That's when I had the idea to use web components in my Next.js (React/Preact) app for the first time ever!

We'll use https://github.com/craigjennings11/wc-spinners/ as a case study.

Web Components 101

Normally to use a webcomponent in a project you just import it like a library:

  import 'wc-spinners/dist/atom-spinner.js';

and then use it in our JSX/HTML:

<div> 
    <atom-spinner/> Loading
</div>

The web component will encapsulate behavior and styles without the weight of a framework, which is very nice for us.

However when it comes to Next.js and TypeScript, we run into some problems.

TypeScript and Web Components

When you use TypeScript with JSX, it tries to check every element you use to guard against typos. This is a problem when you've just registered a component that of course doesn't exist in the normal DOM:

Property 'atom-spinner' does not exist on type 'JSX.IntrinsicElements'.ts(2339)

The solution I got from this guy is to use declaration merging to extend TypeScript:

// declarations.d.ts - place it anywhere, this is an ambient module
declare namespace JSX {
  interface IntrinsicElements {
    "atom-spinner": any;
  }
}

Next.js and Web Components

The next issue you run into is server side rendering.

ReferenceError: HTMLElement is not defined

WC's famously don't have a great SSR story. If you need to SSR WC's, it seems the common recommendation is to use a framework like Stencil.js. I have no experience with that.

Since in my usecase I only needed the WC clientside, I found that I could simply defer loading the WC:

function Component() {
  React.useEffect(() => import("wc-spinners/dist/atom-spinner.js")
  , [])
  return (<div>
        // etc
        <atom-spinner />
        </div>)
}

And that's that! Enjoy using the platform!


Webmentions

Failed to load...