Library / SDK
Paciolan/remote-component avatar
Paciolan/remote-component

remote-component: rendering a React component fetched from a URL

Dynamically load a React Component from a URL

639 stars47 forksTypeScriptMIT

At a glance

What is it?
remote-component loads a React component at runtime from a URL and renders it like any other component, with shared dependencies injected through a config file. It solves code delivery for a plugin style UI, and it takes on the security and caching consequences that come with executing code fetched over the network.
Who is it for?
Adopt remote-component when you need to change a piece of a React UI without shipping a new bundle, and you control the origin serving the component and can pin the versions of the shared dependencies. Do not adopt it for components fetched from a source you do not control, because the loaded code runs with your application's privileges.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A component that arrives at runtime instead of at build time

Most React code is decided when you run your bundler. remote-component inverts that: it loads a component from a URL at runtime and gives it back as a normal React element you can render.

The usage is deliberately plain. You point a RemoteComponent at a URL and pass it props like any other component:

javascript
const url =
  "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js";

const HelloWorld = ({ name }) => <RemoteComponent url={url} name={name} />;

const Container = (
  <>
    <HelloWorld name="Remote" />
  </>
);

The point of the abstraction is that the caller does not care where the component came from. Once loaded, it behaves like a local component, which means a team can move a piece of UI out of the main bundle and into a separately served file without changing how the rest of the application consumes it.

That is a genuinely useful pattern for a class of problems: a widget whose behaviour changes more often than the surrounding app, a plugin surface where third parties supply UI, or a deployment where you want to fix a component without triggering a full frontend release.

It also carries the obvious weight. The component is code fetched over the network and executed in the user's browser with the same privileges as the rest of your app. The library's job is to make that work; making it safe is your job.

The package is MIT licensed, published as @paciolan/remote-component, and written in TypeScript with the build step being tsc.

The dependency contract that makes a remote component possible

A remote component cannot carry its own copy of React, because two Reacts in one tree break hooks and context. The library's answer is an explicit contract with three rules.

The React application and the remote component can share dependencies, and those dependencies must be configured explicitly rather than left to chance. Shared dependencies inside the remote component must be marked as external so they are not bundled into the output. And all shared dependencies must be provided by the React application.

The mechanism is a file called remote-component.config.js in the root of the React application, or inside src for frameworks such as Create React App. The location can be changed inside webpack.config.js. The file supplies the remote components with the external dependencies they need:

javascript
module.exports = {
  resolve: {
    react: require("react")
  }
};

With webpack, a resolve alias makes the file reachable:

javascript
module.exports = {
  resolve: {
    alias: {
      "remote-component.config.js": __dirname + "/remote-component.config.js"
    }
  }
};

Projects without webpack can still use a remote component through manual configuration. You create src/RemoteComponent.js, build the requires object from the config and hand it to the factory:

javascript
import {
  createRemoteComponent,
  createRequires
} from "@paciolan/remote-component";
import { resolve } from "../remote-component.config.js";

const requires = createRequires(resolve);

export const RemoteComponent = createRemoteComponent({ requires });

Then the import in your application changes to point at that local file:

code
import { RemoteComponent } from "./RemoteComponent";

This explicit injection is the design decision that separates this library from a naive eval of fetched code. Every shared dependency is named, versioned by your application, and supplied by you.

Three ways to consume a component: element, render prop and hook

The library offers increasing amounts of control, and the choice between them is mostly about how much you need to know about loading and failure.

The simplest form is the component itself, used like a regular React component with a url prop and any other props you want to pass through:

javascript
import React from "react";
import ReactDOM from "react-dom";
import { RemoteComponent } from "@paciolan/remote-component";

const element = document.getElementById("app");
const url = "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"; // prettier-ignore

const HelloWorld = props => <RemoteComponent url={url} {...props} />;

ReactDOM.render(<HelloWorld name="Paciolan" />, element);

When you need more control over the error or the rendering, a render prop exposes the error and the loaded component:

javascript
const HelloWorld = props =>
  <RemoteComponent
    url={url}
    render={({ err, Component }) =>
      err ? <div>{err.toString()}</div> : <Component {...props} />
    }
  />
);

For the most control, you build your own hook with createUseRemoteComponent, again passing the requires object:

javascript
import {
  createRequires,
  createUseRemoteComponent
} from "@paciolan/remote-component";
import { resolve } from "../../remote-component.config.js";

const requires = createRequires(resolve);

export const useRemoteComponent = createUseRemoteComponent({ requires });

The hook returns a tuple of loading state, error and component, and the example handles each case explicitly:

javascript
import { useRemoteComponent } from "./useRemoteComponent";

const url = "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"; // prettier-ignore

const HelloWorld = props => {
  const [loading, err, Component] = useRemoteComponent(url);

  if (loading) {
    return <div>Loading...</div>;
  }

  if (err != null) {
    return <div>Unknown Error: {err.toString()}</div>;
  }

  return <Component {...props} />;
};

The hook also accepts a second argument for a named import, so the remote module can export the component under a specific name rather than the default:

javascript
const HelloWorld = props => {
  const [loading, err, Component] = useRemoteComponent(url, "customImportName");

  if (loading) {
    return <div>Loading...</div>;
  }

  if (err != null) {
    return <div>Unknown Error: {err.toString()}</div>;
  }

  return <Component {...props} />;
};

Between the three, the error path is the part that deserves a design decision on your side, since a remote component that fails to load is a failure the user sees.

The custom fetcher is the escape hatch, and it is marked advanced

The library exposes a way to replace how the remote code is fetched, and it is described as a feature for advanced users only.

What it does is expose the fetcher from the underlying loader, @paciolan/remote-module-loader, and the documentation sends you to that project's own documentation for the details. The shape of the interface is a function that takes a URL and returns a promise for the module source:

javascript
const fetcher = url => axios.get(url).then(request => request.data);

fetchRemoteComponent({ url, requires, fetcher });

That example shows the whole mechanism in three lines. The default fetcher is replaced with one that uses axios and resolves to the response body, and the fetch call itself is passed the url, the requires object and the fetcher.

The reasons to override it are practical rather than cosmetic. You may need authentication headers, since the default fetch of a component URL will not carry your session. You may want retries or a timeout. You may want to read the component from a local file or from an in-memory cache in tests. And you may want to reject components that fail a checksum or come from an unexpected host.

The dependency list in package.json confirms the layering. The runtime dependencies are just one entry, @paciolan/remote-module-loader at 3.2.2 or later, which means the loading, evaluation and dependency resolution logic lives in a separate package that this library presents with a React shaped interface.

That separation is a good sign for a library whose core job is executing remote code. The risky part has its own project, its own documentation and its own release line.

What the repository contains beyond the library itself

The repository is more than the published package, and the extra files explain how the project is run.

There is an examples directory with a remote-components subdirectory, and the URL used throughout the documentation points into it, at the HelloWorld.js example on the raw.githubusercontent.com host for the master branch. That file is a real, working remote component, which means you can test the whole flow against it without writing anything.

The tooling is a modern JavaScript setup. The build script is tsc, the clean script removes the coverage and dist directories, and there is a watch script that builds with the watch flag. Tests run under jest, with a changed-only variant using the changedSince flag and a coverage variant. Linting is eslint, and formatting is handled through prettier with eslint-plugin-prettier, so lint and format are the same command in practice.

Commit conventions are enforced by tooling rather than by convention alone. There is a commitlint.config.js and a @commitlint/config-conventional dependency, a changelog.config.js, and husky with a prepare script that installs the git hooks. The release process is automated too: the package.json release configuration points at a GitLab repository on the master branch, uses the standard semantic-release plugins for commit analysis, release notes and npm publishing, and a tag format of the version alone.

Version numbering follows the same convention: the version field reads 0.0.0-semantic-versioning, which is the placeholder that semantic-release replaces at publish time. There are no GitHub releases in this repository, because publishing happens to npm through that pipeline.

There are also files such as CLAUDE.md, a .vscode directory, a .browserslistrc, a .babelrc, a .prettierrc.yml and a .npmignore, plus a .gitlab-ci.yml, which indicates the continuous integration runs on GitLab rather than GitHub Actions.

Why remote code delivery is a trade, not a free win

The honest assessment of this library starts with what it costs.

The remote component is fetched over the network at runtime, which means it is not in your bundle, not covered by your bundler's tree shaking, and not part of the artefact your build pipeline produced. Its size and its load time are paid by the user on every cold start unless you add caching, and the library documentation has a section on Content Security Policy, which is the right place to look, because your policy has to permit the script source you serve components from.

The second cost is the security model. Code fetched from a URL runs in the user's browser with access to your application, its data and its authenticated session. The library gives you a place to inject dependencies and a hook to swap the fetcher, and it puts the loading machinery in a separate package, but it does not verify what the fetched code contains. If that URL can be influenced by an attacker, you have handed them your application.

That is why the dependency contract matters as much as it does. Because the remote component cannot bundle its own React and must use the versions your application provides, a hostile or simply mismatched remote file fails loudly at load time rather than quietly producing two Reacts. The external marking and the explicit resolve map are a form of type checking across the network boundary.

The alternative to this whole approach is obvious: build the component into your bundle and ship a new release. For an application that deploys weekly, that is simpler, faster for users and free of runtime code execution risk. The pattern earns its complexity when the component must change independently of the app, when several teams own different parts of the UI, or when you want a plugin surface for code you did not write.

Editorial conclusion

Adopt remote-component when you need to change a piece of a React UI without shipping a new bundle, and you control the origin serving the component and can pin the versions of the shared dependencies. Do not adopt it for components fetched from a source you do not control, because the loaded code runs with your application's privileges. Before deploying, write the dependency list into remote-component.config.js deliberately, confirm your Content Security Policy allows the script source you serve from, and check the last push date, which was 2026-04-06, before you rely on the project.

Frequently asked questions

How do I install and use remote-component in a React application?

Install it with npm install @paciolan/remote-component, then render a RemoteComponent with a url prop. Shared dependencies have to be supplied explicitly through a remote-component.config.js file that lists them under resolve.

How do I share React with a remote component?

List React in the resolve map of remote-component.config.js so it is treated as external and is not bundled with the remote component, and add a resolve alias in webpack.config.js so the config file can be loaded. Projects without webpack can wire the same file up manually in src/RemoteComponent.js.

Can I control how the remote component is fetched?

Yes, through the custom fetcher, which the documentation marks as a feature for advanced users. It exposes the fetcher from the underlying @paciolan/remote-module-loader package and accepts a function that takes a URL and returns a promise for the module source.

How do I handle loading and errors for a remote component?

Use the render prop to get the err and Component values directly, or build a custom useRemoteComponent hook with createUseRemoteComponent, which returns a tuple of loading state, error and component so you can render each case yourself.

How are remote-component releases published?

Publishing is automated with semantic-release using the commit-analyzer, release-notes-generator and npm plugins, with husky and commitlint enforcing the commit format. The version field in package.json reads 0.0.0-semantic-versioning and is filled in at publish time, and there are no GitHub releases.

Official sources

  1. Issues
  2. License: MIT
  3. Paciolan/remote-component on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/paciolan-remote-component.svg)](https://hysenlabs.com/projects/paciolan-remote-component)

Was this useful?