# Chrome Extension Boilerplate React Vite: Content Scripts vs Content-UI vs Content-Runtime Explained

> Understand content scripts content-ui and content-runtime in the Chrome extension boilerplate React Vite. Learn JS injection UI overlays and dynamic scripting.

- Repository: [JongHak Seo/chrome-extension-boilerplate-react-vite](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite)
- Tags: deep-dive
- Published: 2026-03-05

---

**The `chrome-extension-boilerplate-react-vite` repository provides three distinct content script strategies: `content` for plain JavaScript injection, `content-ui` for React-based UI overlays, and `content-runtime` for dynamic on-demand script injection via the Chrome scripting API.**

This React Vite chrome extension boilerplate organizes content scripts into three specialized directories under `pages/`, each serving different injection patterns and build requirements. Understanding these distinctions ensures you choose the right architecture for your extension's content script needs.

## Understanding the Three Content Script Types

### Content: Static Plain JavaScript Injection

The `content` directory contains **static content scripts** that inject plain JavaScript into matching pages automatically when the page loads. These scripts are ideal for simple DOM manipulation, logging, or background-style code that requires no UI components.

In [`chrome-extension/manifest.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/chrome-extension/manifest.ts), these scripts are declared under the `content_scripts` array with entries like `js: ['content/all.iife.js']` (lines 50-60). The build pipeline compiles these via the regular Vite build process for the content page, outputting immediately invoked function expression (IIFE) bundles that execute automatically upon injection.

### Content-UI: Static React Component Injection

The `content-ui` directory provides **static UI content scripts** that inject bundled React components into matching pages automatically when the page loads. This approach is designed for UI overlays, toolbars, or any visual element that must render directly on the host page using React and Tailwind CSS.

Like the plain content scripts, these are declared in [`chrome-extension/manifest.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/chrome-extension/manifest.ts) under `content_scripts`, but they point to separate UI bundles such as `js: ['content-ui/all.iife.js']` (lines 61-66). The build pipeline compiles these with full React and Tailwind support, outputting IIFE bundles that mount a React root into the page DOM.

### Content-Runtime: Dynamic On-Demand Injection

The `content-runtime` directory contains **dynamic content scripts** that are not declared in the manifest and can be injected on demand from any extension page (popup, background, options, etc.) using the Chrome `scripting.executeScript` API.

Unlike the static variants, these scripts have no entry in [`manifest.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.ts). Instead, the `pages/content-runtime/build.mts` script generates bundles in `dist/content-runtime/` that can be referenced at runtime. This architecture supports features triggered by user actions or runtime conditions, such as injecting a script only after a user clicks a popup button or enabling a feature through the options page.

## Build and Declaration Differences

### Manifest Configuration for Static Scripts

Static content scripts require explicit declaration in the manifest. The boilerplate's [`chrome-extension/manifest.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/chrome-extension/manifest.ts) demonstrates this pattern:

```typescript
// chrome-extension/manifest.ts (lines 50-66)
content_scripts: [
  {
    matches: ['http://*/*', 'https://*/*', '<all_urls>'],
    js: ['content/all.iife.js'],      // Plain JS content script
  },
  {
    matches: ['http://*/*', 'https://*/*', '<all_urls>'],
    js: ['content-ui/all.iife.js'],   // React UI content script
  },
],

```

### Runtime Build Pipeline

The content-runtime scripts use a specialized build process defined in `pages/content-runtime/build.mts`. This script generates both JavaScript and CSS files for each match folder:

```typescript
// pages/content-runtime/build.mts (excerpt)
const configs = Object.entries(getContentScriptEntries(matchesDir)).map(
  ([name, entry]) => ({
    name,
    config: withPageConfig({
      lib: { name, formats: ['iife'], entry, fileName: name },
      outDir: resolve(rootDir, '..', '..', 'dist', 'content-runtime'),
    }),
  })
);

```

Each entry produces a `<name>.iife.js` file suitable for execution via `chrome.scripting.executeScript`.

## Practical Implementation Examples

### Injecting a Runtime Script from the Popup

To inject a content-runtime script on demand, use the Chrome scripting API from your popup or background script:

```typescript
// pages/popup/src/Popup.tsx
async function injectRuntime() {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  if (tab?.id) {
    await chrome.scripting.executeScript({
      target: { tabId: tab.id },
      files: ['/content-runtime/example.iife.js'], // Built by content-runtime/build.mts
    });
  }
}

```

### Creating a Runtime UI Component

Runtime scripts can also include React components. The boilerplate provides an example in [`pages/content-runtime/src/matches/example/App.tsx`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/pages/content-runtime/src/matches/example/App.tsx), which demonstrates how to structure a component that will be bundled as an IIFE for runtime injection.

## Summary

- **Content scripts** (`pages/content/`) provide automatic injection of plain JavaScript for DOM manipulation and background-style logic, declared in [`manifest.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.ts) under `content_scripts`.

- **Content-UI scripts** (`pages/content-ui/`) provide automatic injection of React components with Tailwind styling for visual overlays, also declared statically in the manifest but built with React support.

- **Content-Runtime scripts** (`pages/content-runtime/`) provide dynamic, on-demand injection via the Chrome `scripting.executeScript` API, with no manifest declaration required, enabling user-triggered or conditional script execution.

## Frequently Asked Questions

### When should I use content-runtime instead of content or content-ui?

Use **content-runtime** when you need to inject scripts based on user actions or runtime conditions rather than automatic page load injection. For example, if your extension only needs to modify a page after the user clicks a button in the popup, content-runtime avoids the performance cost of loading unused scripts on every page. In contrast, use **content** or **content-ui** when the functionality must be available immediately when the page loads.

### Can content-runtime scripts include React components like content-ui?

Yes, content-runtime scripts can include React components. The boilerplate's [`pages/content-runtime/src/matches/example/App.tsx`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/pages/content-runtime/src/matches/example/App.tsx) demonstrates a React component structure that gets bundled as an IIFE. However, unlike content-ui scripts which automatically mount into the page, runtime scripts must manually handle React root creation and DOM insertion when executed via `chrome.scripting.executeScript`.

### How do I add a new content-runtime script to the build pipeline?

Create a new folder under `pages/content-runtime/src/matches/` containing your entry point (e.g., [`index.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/index.ts) or [`App.tsx`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/App.tsx)). The `pages/content-runtime/build.mts` script automatically discovers these folders using `getContentScriptEntries()` and generates corresponding IIFE bundles in `dist/content-runtime/`. Each folder becomes a separate bundle that you can reference by name in `chrome.scripting.executeScript` calls.

### Do content-runtime scripts have access to the Chrome extension APIs?

Yes, content-runtime scripts execute in the content script context of the target tab, giving them access to the standard content script APIs including `chrome.runtime.sendMessage` for communicating with the background script. However, they must be injected into a specific tab using `chrome.scripting.executeScript` before they can execute, unlike statically declared content scripts which are injected automatically based on manifest match patterns.