Mastering Browser Extensions: A Step-by-Step Guide with React.js
Browser extensions enhance web experiences by adding new functionality, and
with React.js, building modern, dynamic UI inside them is more seamless for a stronger expression.
ποΈ Architecture Overview
A React-powered browser extension typically has these components:
Manifest file (
manifest.json)Background Script β Handles events, persistent logic
Content Script β Injects code/UI into the active webpage
Popup / Options Page β React-based UI
React App β Built with Vite or CRA and bundled for use in the extension
π Flow Diagram
Hereβs a simplified data and control flow:
π§± Folder Structure
pgsqlCopyEditmy-extension/
β
βββ public/
β βββ manifest.json
βββ src/
β βββ background.ts
β βββ content.ts
β βββ popup/
β β βββ Popup.tsx
β βββ App.tsx
βββ package.json
βββ vite.config.js
βββ tsconfig.json
π§ͺ Sample Code
manifest.json (v3)
{
"manifest_version": 3,
"name": "React Extension Example",
"version": "1.0",
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"permissions": ["storage", "tabs", "scripting"]
}
React Popup: popup/Popup.tsx
import React from 'react';
export default function Popup() {
return (
<div style={{ padding: 10 }}>
<h2>π§ Extension Popup</h2>
<button onClick={() => alert('Clicked!')}>Click Me</button>
</div>
);
}
β
src/App.tsx
tsxCopyEditimport React from 'react';
function App() {
const handleClick = () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tabId = tabs[0]?.id;
if (tabId) {
chrome.scripting.executeScript({
target: { tabId },
func: () => alert('Hello from ByteForge UI!'),
});
}
});
};
return (
<div style={styles.container}>
<h1 style={styles.heading}>π§ ByteForge UI</h1>
<p style={styles.description}>Your React-powered browser extension is running!</p>
<button onClick={handleClick} style={styles.button}>
Inject Alert
</button>
</div>
);
}
const styles = {
container: {
padding: '20px',
width: '300px',
fontFamily: 'sans-serif',
backgroundColor: '#1e1e1e',
color: '#fff',
borderRadius: '8px',
} as React.CSSProperties,
heading: {
margin: 0,
fontSize: '1.5rem',
},
description: {
fontSize: '0.9rem',
margin: '10px 0',
},
button: {
backgroundColor: '#3B82F6',
color: '#fff',
border: 'none',
borderRadius: '5px',
padding: '10px 12px',
cursor: 'pointer',
},
};
export default App;
Background: background.ts
chrome.runtime.onInstalled.addListener(() => {
console.log('Extension Installed');
});
π¦ package.json
{
"name": "react-browser-extension",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/chrome": "^0.0.261",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.7",
"typescript": "^5.2.2",
"vite": "^5.0.0",
"vite-plugin-crx": "^0.5.0",
"vite-plugin-react": "^3.1.0"
}
}
βοΈ vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { crx } from 'vite-plugin-crx';
import manifest from './public/manifest.json';
export default defineConfig({
plugins: [
react(),
crx({ manifest })
],
build: {
rollupOptions: {
input: {
popup: 'src/popup/index.html',
content: 'src/content.ts',
background: 'src/background.ts',
},
}
}
});
βοΈ Replace input paths with your actual file structure.
π§ tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
π οΈ Build the Project
In your terminal:
npm install
npm run build
This will generate a
dist/folder with all compiled assets.
π§© Add Extension to Chrome
Open Chrome and go to
chrome://extensions/Turn on Developer Mode (top-right)
Click "Load unpacked"
Select the
dist/folder generated by Vite
Your extension should now appear in the Chrome extension toolbar!
π§ Pros & Cons
| Pros | Cons |
| β React makes UI development fast | β More build steps than plain JS |
| β Hot reloading with Vite | β Needs bundling and config setup |
| β Component reuse across popup/options | β Some APIs are not React-friendly |
| β Can integrate with Redux/Context API | β Browser API limitations |
π Browser Support Table
| Feature | Chrome | Firefox | Edge | Safari |
| Manifest v3 | β | β * | β | β |
| React (via bundler) | β | β | β | β |
| Service Workers in bg | β | β | β | β |
| Content Scripts | β | β | β | β |
| Declarative Net Request | β | πΆ | β | β |
πΆ = Partial or under development
β = Fully Supported
β = Not Supported
Thank You
Please comment and share if there is any issue/improvement and please motivate me by like share this. I will come with more interesting simple ByteForge UI