Chrome Extension Boilerplate React Vite: Content Scripts vs Content-UI vs Content-Runtime Explained
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, 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 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. 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 demonstrates this pattern:
// 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:
// 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:
// 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, 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 inmanifest.tsundercontent_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 Chromescripting.executeScriptAPI, 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 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 or 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →