How to render a Component hosted in S3 inside of a React / Next Application?

21 Views Asked by At

We are trying to implement a feature allowing users to add components hosted in an S3 file inside a Next / React application.

The application allows users to create small websites and apps (Projects) using a "Block style" architecture. Users can edit content in real-time using a drag-and-drop approach.

We want to enable users to create their own components and add them to their projects. Examples of components include a data table, an image and text section, a chatbox, etc.

Example of component in a project

Our first approach consists of storing the JSX markup code (Compiled) for the component inside of an S3 file; and dynamically fetch the file in the client when requested.

It doesn't seem to work, though. When logged, the code content of the file is correct, but it doesn't render.

The component doesn't render

We're trying different solutions, but I wonder if we shouldn't take an entirely different approach.

  • Should we host component files as entire react projects in S3 rather than just the "markup" code?
  • Or maybe not use S3?

Here is a working Sandbox

Below is the code we currently use to fetch content from the S3 file and render it:

import React, { useState, useEffect, ComponentType } from "react";
// import { _jsx as _jsx, _jsxs as _jsxs } from "react/jsx-runtime";
// import * as JSXRuntime from "react/jsx-runtime";


const RenderRemoteComponent = ({}) => {
  const [Component, setComponent] = useState<ComponentType<any> | null>(null);

  const MyComponent =
    "https://boltcomponents.s3.us-east-1.amazonaws.com/compiled-test.js?response-content-disposition=inline&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEAgaCXVzLXdlc3QtMSJHMEUCIC0647aBXXIIKGkE7XemO3V1MFb7bILQS0fhrbmS85T3AiEA5lccIZqFzQFA9irKSov5uIN%2FWKmbTFarUik3OQnLKBgq6AIIIRAAGgwyMTk0Mzk3MjcwMTQiDE5LosSgzmIlR6EkRyrFAsvHEGDpoEAECnymKc8ck2EX8FA6qibAK54DvqkI%2FT2E6UnxB01bfs%2Fm2h9pUHsGZbPDRFkABwnPE0O32Twt%2Bq4zNHAYbQJCGU53ReJBfmT%2B1%2B3C4umykFl9Li1DmR6Bi4KJUZdD4ZeKxu3FIW%2FGzOZviZCpLw%2FGDnJ3%2BPDpH4OHdJvDijbB%2FY1oS1SUZEzOfPguMLSbjKvaQfnqocYjbHwDiBIAJNiBcZm7plfgCTT1fFC5BBBTAdYWvBLbtVV5OIuBcJ1Zjr4soTJpLu4%2Fxl1H7LpxSbgidFK6BRFyNF0L%2FScIjeaOqgH%2BMy9a6Ync9GyozW2bspZvuahkSvMix%2FZzjP2DxD8OPwP6fhPUxyZsyXKSl06kDybDnWlpmBZxX7btHQMa0rmrSaSkKrME2tKOj%2FMftSXJSuosF3OeZab5CGUUbBMwi%2FLtrwY6swJfOvblt7hBtemPejsolXmvbvPJ2297DnOLsGyfYepS90PoVobw%2BIRG3qjyee3B2AZGtJuGS2DTbW0hmfiS4eGabnCKjwsr1C%2BxLycQvcsf1sIaLp8q2zzrmjVwOV2m%2FfzfMONmFK0%2B4N6HpMYg5QT4T9bYMnSu%2FN%2FBlSbK78pzDwNmeSfaBSBG5AfTrusWptr681xKvIyT8%2BjZnRE8CzPiburq7GKJpFtiCQqK1onOTz%2BaxtrHM4pzpafPcX3Xs%2BZJU8d4T%2BBXQ%2BQuQ%2Fs82q%2FymfsdAppZvyTI5kd7ATqJBVfneKYCARWinBnitxJWJL9QcefsYqh4TRcSDjU0n5cpHqUtm0RCTWuw7CccR5%2FuezNt1rp%2FD965LPYgbD%2FmKttj4ytylf1rx8g1V25eYCY7h1ur&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240321T011938Z&X-Amz-SignedHeaders=host&X-Amz-Expires=43199&X-Amz-Credential=ASIATGF5AQGTOFLFGNFD%2F20240321%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=155c73bedbe27d1503bd4264a2ffb7e2d80bfe7827701e5b797d7fe2a218a1c4";

  useEffect(() => {
    fetch(MyComponent)
      .then((res) => res.text())
      .then((jsCode) => {
        console.log("Fetched JS Code:", jsCode);
        // Provide React and JSX runtime methods in the scope of the evaluated code
        const Module = new Function("React", "JSXRuntime", "return " + jsCode);
        // Assuming the module's default export is the component
        const ComponentModule: { default: ComponentType<any> } = Module(React, JSXRuntime);
        setComponent(() => ComponentModule.default);
      })
      .catch((error) => {
        console.error("Failed to load and compile the component:", error);
      });
  }, [MyComponent]);

  if (!Component) {
    return <div>Loading component...</div>;
  }

  // Using 'as' to assert the component type
  return <Component />;
};

export default RenderRemoteComponent;

Below is the code stored in the S3 file:

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";

// Define the component
const MyComponent = () => {
  return /*#__PURE__*/ _jsxs("div", {
    className: "block relative border rounded-lg overflow-hidden",
    children: [
      /*#__PURE__*/ _jsx("div", {
        className: "w-full bg-overlay z-0",
        children: /*#__PURE__*/ _jsx("img", {
          src: "https://images.unsplash.com/photo-1708257106465-7e2b14997ab9?q=80&w=2244&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
        }),
      }),
      /*#__PURE__*/ _jsxs("div", {
        className: "p-5 z-50 w-full h-full ",
        children: [
          /*#__PURE__*/ _jsx("h5", {
            className: "mb-2 text-xl font-medium leading-tight ",
            children: "Example Title",
          }),
          /*#__PURE__*/ _jsx("p", {
            className: "mb-4 text-base",
            children: "This is example text for the content area.",
          }),
          /*#__PURE__*/ _jsx("button", {
            type: "button",
            className:
              "bg-blue-500 inline-flex items-center justify-center text-white whitespace-nowrap rounded-md text-sm font-medium h-10 px-4 py-2",
            children: "Click Here",
          }),
        ],
      }),
    ],
  });
};

// Export the component
export default MyComponent;
0

There are 0 best solutions below