Adrian J
Adrian J

Reputation: 9

How to bundle my NextJS React component only so I can inject it in any website?

I am using NextJS 14.2.3 and I want this test component which depends on the tailwindcss and react to be injected in a Wordpress/any kind of website.

import React, { useEffect, useState } from 'react'

export default function Test() {
    const [index, setIndex] = useState(0);
  return (
    <div className='bg-blue-100 p-8'>
        <button onClick={() => setIndex(index + 1)}>Click</button>
        {index}
    </div>
  )
}

Below is the directory tree of how the result I want to be.

frontend/
├── app/
│   └── test.tsx
├── public/
│   └── test-bundle.js
├── package.json
└── next.config.mjs

Why? I want to understand on the most basic level, how to do this because I have tickets component which depends on Stripe, Tailwind, lucide-react and NextJS server actions (which communicate with my backend). Which should be injectable in every website, the backend will take care of CORS.

I have been reading blogs, documentations and chating with chatGPT 4 about how to achieve this. I've tried using webpack, which compiled the test component in the /public directory but when i tried to inject it in plain vanilla website but I got console errors which I tried to resolve but it still didn't work.

Upvotes: 0

Views: 90

Answers (1)

Adrian J
Adrian J

Reputation: 9

To achieve an injectable React component from a Next.js project that can be used in any existing website, I followed these steps. My component depended on various libraries like React, Stripe, Radix-UI, Stripe, and even Next.js server actions. I will showcase only the ticket.tsx component as I use this project for commercial purposes.

Here's how I accomplished it:

Below is the structure of my parent component located in app/(marketing)/tickets. This component includes server actions, child components, and relies on libraries like Radix-UI:

'use client';
import React, { useState } from 'react';
import Steps from './steps';
import Step from './step';
import { Calendar } from '@/components/ui/calendar';
import TicketsCalculator from './ticketsCalculator';
import { useTicketStore } from '@/hooks/useTicketStore';
import { hr } from 'date-fns/locale';
import TicketsGuest from './ticketsGuest';
import TicketsPayment from './ticketsPayment';

export default function Tickets() {
  const [activeIndex, setActiveIndex] = useState(0);
  const date = useTicketStore(state => state.date);
  const setDate = useTicketStore(state => state.setDate);

  const handleIndex = (e: number): void => {
    setActiveIndex(e);
  };

  const steps = [
    { label: 'Select date', description: 'Select the date of your desired trip day' },
    { label: 'Tickets', description: 'Enter ticket numbers and information' },
    { label: 'Guest Info', description: 'Provide guest details' },
    { label: 'Payment', description: 'Complete payment for your tickets' },
  ];

  return (
    <div className="relative h-full w-full rounded-2xl bg-neutral-50 flex flex-col antialiased">
      <div className="p-4 md:p-6 z-50 w-full h-full flex flex-col">
        <Steps model={steps} activeIndex={activeIndex} onSelect={(e) => handleIndex(e)} />
        {activeIndex === 0 && (
          <Step activeIndex={activeIndex} changeActiveIndex={(e) => handleIndex(e)}>
            <Calendar
              mode="single"
              numberOfMonths={3}
              selected={date}
              onSelect={setDate}
              locale={hr}
              className="rounded-2xl w-fit mx-auto border bg-white hidden md:block"
            />
          </Step>
        )}
        {activeIndex === 1 && (
          <Step activeIndex={activeIndex} changeActiveIndex={(e) => handleIndex(e)}>
            <TicketsCalculator />
          </Step>
        )}
        {activeIndex === 2 && (
          <TicketsGuest activeIndex={activeIndex} changeActiveIndex={(e) => handleIndex(e)} />
        )}
        {activeIndex === 3 && (
          <TicketsPayment activeIndex={activeIndex} changeActiveIndex={(e) => handleIndex(e)} />
        )}
      </div>
    </div>
  );
}

Webpack Configuration

To bundle the Tickets component into a JavaScript file (tickets-bundle.js) that can be injected into other websites, I used Webpack. Here's the webpack.config.js:

const path = require('path');
const webpack = require('webpack');
const dotenv = require('dotenv');

// Load environment variables from .env
const env = dotenv.config().parsed || {};

module.exports = {
  entry: './app/(marketing)/tickets/tickets.tsx', // Entry point for the Tickets component
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'tickets-bundle.js', // Bundled file name
    library: {
      name: 'TicketsComponent', // Global variable name
      type: 'umd', // Universal Module Definition
    },
    globalObject: 'this', // Ensure compatibility in different environments
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/, // Handle TypeScript and TSX files
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
          },
        },
      },
      {
        test: /\.css$/i, // Process CSS files
        use: ['style-loader', 'css-loader', 'postcss-loader'],
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.ts', '.tsx'],
    alias: {
      '@': path.resolve(__dirname), // Alias for project root
    },
  },
  externals: {
    react: 'React', // Use React from the host environment
    'react-dom': 'ReactDOM', // Use ReactDOM from the host environment
  },
  plugins: [
    new webpack.ProvidePlugin({
      process: 'process/browser', // Polyfill for Node.js process
    }),
    new webpack.DefinePlugin({
      'process.env': JSON.stringify(env), // Inject environment variables
    }),
  ],
  mode: 'production', // Build mode
};


Building the Bundle

Required packages: npm install dotenv process @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript babel-loader css-loader style-loader webpack webpack-cli to run webpack so it can build the bundle using this command:

npx webpack --mode production

This creates the tickets-bundle.js file in the public directory.

Testing if works

Basically, to test, I've created a test folder on my laptop which has 2 files, index.html and tickets-bundle.js which the code I've copied from the one that Webpack has created. The index.html looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Tickets Component</title>
  <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
  <script src="/tickets-bundle.js"></script>
</head>
<body>
  <div id="tickets-root"></div>
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      if (typeof TicketsComponent !== "undefined" && TicketsComponent.default) {
        ReactDOM.render(
          React.createElement(TicketsComponent.default),
          document.getElementById("tickets-root")
        );
      } else {
        console.error("TicketsComponent is not defined. Make sure tickets-bundle.js is loaded properly.");
      }
    });
  </script>
</body>
</html>

In production, instead of having

 <script src="/tickets-bundle.js"></script> 

it will be

<script src="https://my-domain.com/tickets-bundle.js"></script> 

which will allow me to inject the tickets component in the existing website that the client has.

Upvotes: 0

Related Questions