Loading...

How to add a custom element to a Next.js/Typescript project?

question typescript react
Ram Patra Published on September 28, 2024

Let’s say I have a custom element setapp-badge that I want to use in my tsx files. If I go ahead and use it, the compiler will throw an error and Next.js will fail to build. It seems the problem might be a combination of how Next.js, TypeScript, and custom elements work together. Therefore, let’s try an approach that avoids the namespace/module issues while ensuring custom elements are recognized in a Next.js/TypeScript project.

Using @types/react Augmentation and Ensuring Proper Recognition of Custom Elements

Instead of extending JSX.IntrinsicElements or HTMLElementTagNameMap, we will focus on properly extending the React types for custom elements directly in a way that Next.js can handle.

Step 1: Ensure TypeScript Knows the Custom Element

Place the following in a file, say custom-elements.d.ts in the root or src/types/:

// custom-elements.d.ts
declare namespace JSX {
  interface IntrinsicElements {
    'setapp-badge': {
      appId: string;
      vendorId: string;
      theme: string;
    };
  }
}

Why this should work

  1. Declaring IntrinsicElements within the JSX namespace directly ensures Next.js and TypeScript treat setapp-badge as a valid intrinsic element.
  2. No use of declare module or namespace: We avoid augmenting the React module itself, which seems to be the root of the issue.

Step 2: Verify tsconfig.json Includes the Declarations

Ensure your tsconfig.json file includes the declaration file:

{
  "compilerOptions": {
    // other options
  },
  "include": [
    "custom-elements.d.ts",
    "src/**/*"
  ]
}

Step 3: Use setapp-badge in Your JSX

Now you can use your custom element in any .tsx file like this:

<setapp-badge appId="401" vendorId="285" theme="dark"></setapp-badge>

Key Benefits

  • Avoids ESLint Issues: This solution does not trigger the @typescript-eslint/no-namespace rule since we’re not using namespace or declare module.
  • Simple IntrinsicElements Extension: We’re directly extending JSX.IntrinsicElements, which is allowed in TypeScript and recognized by Next.js.
  • Next.js Friendly: No need for advanced or experimental TypeScript configurations.

This approach avoids the problems we’ve encountered before and directly adds the custom element to JSX without creating issues with Next.js.

Why other solutions might fail

  • declare module/namespace: Next.js might not handle these well in TypeScript configurations, leading to the errors.
  • Next.js JSX handling: By extending JSX.IntrinsicElements directly within a declare namespace JSX, we avoid Next.js misinterpreting the custom element.

Update for Next.js 15 and React 19

The above solution failed to work in websites using Next.js 15 and React 19. However, with a slight change, I was able to make it work. The key difference is that in React 19/Next.js 15, you need to use the declare module ‘react’ approach rather than the global namespace declaration that worked in earlier versions.

import 'react'

declare module 'react' {
  namespace JSX {
    interface IntrinsicElements {
      'setapp-badge': {
        appId: string;
        vendorId: string;
        theme: string;
      };
    }
  }
}
Presentify

Take your presentation to the next level.

FaceScreen

Put your face and name on your screen.

KeyScreen

Show keypresses on your screen.

ToDoBar

Your to-dos on your menu bar.

SimpleFill

Fill forms using your right-click menu.

IconSim

Preview your Mac app icons.

Ram Patra Published on September 28, 2024
Image placeholder

Keep reading

If this article was helpful, others might be too

question typescript July 28, 2024 Interface vs Type alias in Typescript with some real-world examples showing when to use what

In TypeScript, both interface and type alias can be used to define the shape of an object. However, there are some differences and nuances between them. Here are the key differences:

question typescript July 20, 2024 How to filter a Map based on a condition in Typescript?

In TypeScript, you can filter a Map based on a condition using the Map’s built-in methods along with some of JavaScript’s array methods. Since Map is iterable, you can convert it to an array, apply the filter, and then convert it back to a Map. Here’s how you can do it:

question typescript July 20, 2024 How to filter an Array based on a condition in Typescript?

Filtering an array based on a condition in TypeScript is straightforward and similar to how you would do it in JavaScript. TypeScript adds type safety to the process, ensuring that your code is more robust and less error-prone.

Like my work?

Please, feel free to reach out. I would be more than happy to chat.