Tutorial: how to embed a react component that is part of a larger app

Last updated on June 2023

How to embed part of a NextJS application

This article assumes you have some basic understanding of React/NextJS

In recent years it is common for small start-ups to have a large app, often using something like NextJS.

And then at some point, there is an idea to embed some part of that functionality.

For example, an application which lets people register, calculate postage costs (an interactive widget), create orders etc. You might want to embed the postage cost calculator.

A quick way to do this would be to copy/paste the calculator component, put it into a new repository, and make an embed out of that.

But then what happens when the main app's calculator gets updated? Do you copy the changes over (manually), trying to keep it in sync? This might work for a while, but as the app grows you will soon see the issues that this comes with.

In this tutorial, I'll go over how to split up the calculator into its own component that your main app can use, and that you can use in a separate smaller app that you can embed (and give a <script> code to people so they can easily add your widget to their site).

There are a few main steps:

  1. Extract the widget out of your existing app, and put it as its own package
  2. Publish this 'widget package' (can be published privately) to npm
  3. Use that 'widget package' in your existing app (so it works the same as before)
  4. Create a new (small) react app that includes your 'widget package', and set it up so you can share a <script> snippet for people to embed it on their site

Step 1 - extracting a widget out of your existing app

This is probably the easiest step. The difficulty is in how coupled your widget code is to other code in your existing app.

It might require quite a large refactor (potentially moving all shared components into a shared package).

For example, you probably have some UI elements like form inputs or buttons that are shared in your main app and the widget component.

This is probably easier if you use a monorepo, split into different packages. Then you can still publish its own component, while easily using these other shared components.

For the sake of this tutorial, I'll keep the examples simple and will assume that you can easily extract your widget into its own component.

These are the rough steps that can achieve this:

First, you want to create a new repository (or a new npm/yarn workspace package).

Then you need to have a way to publish this component as its own package.

I prefer to use rollup (with babel) for this.

Rollup is a module bundler. Some alternatives are webpack or vite.

.babelrc.js

module.exports = {
    presets: [
        "@babel/preset-env",
        "@babel/preset-react",
    ], plugins: [
        "@babel/plugin-proposal-class-properties",
        "@babel/plugin-transform-react-jsx",
    ]
};

rollup.config.js

import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import filesize from "rollup-plugin-filesize";
import localResolve from "rollup-plugin-local-resolve";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "rollup-plugin-node-resolve";
import {terser} from "rollup-plugin-terser";
import replace from "rollup-plugin-replace";
import packageJson from "./package.json";

const config = {
    plugins: [
        resolve({
            extensions: [".js", ".jsx"],
            mainFields: ["module"],
        }),
        peerDepsExternal(),
        babel(),
        localResolve(),
        commonjs(),
        filesize(),
        terser(),
        replace({
            "process.env.NODE_ENV": JSON.stringify("production"),
        }),
    ],
    input: "src/index.jsx",
    output: [
        {
            file: packageJson.browser,
            format: "umd",
            name: "YourPackage",
            globals: {react: "React"},
        },
        {
            file: packageJson.module,
            format: "es",
        },
        {
            file: packageJson.main,
            format: "cjs",
            name: "YourPackage",
        },
    ],
    external: [
        ...Object.keys(packageJson.dependencies),
        ...Object.keys(packageJson.peerDependencies),
    ]
};

export default config;

(note: I based this a little on date-picker).

As for the actual build script in package.json:

{
 "scripts": {
     "build": "rollup -c"
 }
}

Step 2 - publish this widget package to npm

Then the steps to build and release are:

yarn build
npm publish

(You should automate this as part of an auto-publish script but to keep this tutorial I'll skip that).

(You might want to set it to private, in package.json so your package isn't publicly available)

Step 3 - use the newly published package in your existing app

This is a very simple step - you just need to yarn add your-new-package-name, and import it in where the widget was extracted from.

Step 4 - create an embeddable script that uses your widget

Now we are at the more exciting step - giving users a <script> tag that they can use to easily embed your widget component into their site (without them having to set up React etc).

I think the easiest way is actually to just <iframe> it - but then there are many issues with iframes (such as setting the iframe width/height). Webcomponents are another alternative but I think for simplicity I'll stick with a simple react app.

Start by creating a new React app (I'd recommend with Vite in 2023). Run yarn add your-new-package-name to add your new component widget package as a dependency.

Inside your App.tsx file (automatically created with Vite), and import your new component there. Try it out with yarn dev, and it should load ok.

My aim is to be able to give users a small snippet like this:

<!-- Put this where you want our calculator embed to be: -->
<div id="your-company-name-embed"></div>
<script src="//your-site.example/embed.js"></script>

So you should change your main.tsx to include this:

import 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'

function embedYourSite(rootElement: HTMLElement | null) {
    if (!rootElement) {
        throw new Error(
            'Embedding our script requires a <div id="your-company-name-embed">'
        )
    }
    ReactDOM.createRoot(rootElement).render(<App/>)
}

const rootElement = document.getElementById('your-company-name-embed')
embedYourSite(rootElement);

build this, and host the JS that it builds.

Then your users can add this HTML and it will embed your widget in their site.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

<div id="your-company-name-embed"></div>
<script src="//your-site/embed-script.js" ></script>

</body>
</html>

Next steps

This is a very simplified tutorial to put you on the right path, but there are a bunch of things that are needed before any of this is ready for production:

  • testing! You want to add testing for the component widget itself, some integration tests in the 2 apps of yours that use it
  • e2e tests
  • typescript definitions in the package
  • You probably will want to allow users to add in options to your embed script. It is more likely that you will want to do something this:
<script src="//your-site/embed-script.js"></script>
<script>
    embedYourSite(
    document.getElementById('some-id'),
    {clientId: "abc123", currency: "USD"}
    )
</script>
  • Analytics for the embed
  • automatic deployment/publishing of your package
  • documentation
© 2019-2023 a5h.dev.
All Rights Reserved. Use information found on my site at your own risk.