Reputation: 9
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
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