© Copyright zerodays d.o.o.
Chrome extensions are more than just handy little tools - they’re like superpowers for your browser. From automating workflows to enhancing user experience, they can transform the way people interact with the web. But the real beauty? Extensions can seamlessly integrate into websites, modify content on the fly, and even leverage AI for mind-blowing results (like Classmate does).
Instead of a dull definition, let’s highlight what makes extensions exciting:
Unlike traditional web apps, building a Chrome extension usually requires a carefull balancing of performance, compatibility, and maintainability. There are multiple ways to approach extension development, from vanilla JavaScript to full-blown frameworks.
For Classmate, we went with React, TypeScript, Vite, Tailwind, and Supabase, ensuring a modern, scalable architecture with minimal bloat.
Chrome extensions offer incredible browser APIs but also introduce quirks and gotchas that developers need to navigate.
One of the biggest challenges in Chrome extension development is getting different parts of the extension – popup, background scripts, and content scripts – to talk to each other efficiently. Unlike a traditional web app, where everything runs in a single execution context, Chrome extensions are split into isolated environments.
This means:
✅ The popup doesn’t directly control the webpage – it must send messages.
✅ The content script can interact with the page but lacks access to extension APIs – it needs to communicate with the background.
✅ The background service worker acts as a central hub, receiving messages and dispatching commands.
To keep our messaging system structured and scalable, we use typed messages with TypeScript, ensuring every communication has a clear format.
We define a set of commands, like:
export type Command =
| 'START_SCREENSHOT'
| 'ASK_AI'
| 'TRIGGER_AUTOSOLVE'
| 'OPEN_SIDEBAR';
Each message follows a strict format, using interfaces like:
export interface AskAI extends BaseMessage {
command: 'ASK_AI';
text: string;
autoSolveData?: AutoSolveQuestion | null;
}
This ensures every part of our extension knows exactly what kind of messages to expect, reducing errors.
We use a helper function to find and message the active tab:
export const sendMessageToActiveTab = async (message: Message) => {
const tab = await getActiveTab();
if (!tab?.id) throw new Error('No active tab found');
return await sendMessageToTab(tab.id, message);
};
This makes sure we’re always targeting the currently open webpage, whether it’s to trigger AI processing or capture screenshots.
We use React hooks to set up message listeners inside components. This makes handling commands like "START_SCREENSHOT"
declarative and efficient:
useMessageListener('START_SCREENSHOT', () => {
setState('area-picker');
});
This way, the UI updates reactively whenever the popup or background script sends a command.
With typed messages, structured commands, and well-defined listeners, our extension avoids race conditions, unexpected behavior, and debugging nightmares. Instead, every part of the extension stays in sync, making interactions smooth and instantaneous. 🚀
If you’re building Chrome extensions with modern web frameworks, CRXJS is a game-changer. It simplifies the development process by integrating seamlessly with Vite, allowing you to use React (or other frameworks) with minimal hassle.
Traditional Chrome extension development often involves complex configurations, dealing with manifest quirks, and managing different extension environments (background, content scripts, popup). CRXJS abstracts much of this complexity while staying lightweight and performant.
✅ Zero-config setup – Works out of the box with Vite. ✅ Automatic bundling – No need to manually manage content scripts and background scripts. ✅ HMR (Hot Module Replacement) for popups & options pages – Develop faster without needing to reload the extension manually. ✅ TypeScript & React support – Makes modern development workflows much easier.
To get started, install CRXJS Vite plugin:
npm install @crxjs/vite-plugin -D
Then, configure it in vite.config.ts
:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { crx } from '@crxjs/vite-plugin';
import manifest from './manifest.json';
export default defineConfig({
plugins: [react(), crx({ manifest })],
});
This setup ensures that your extension files are properly processed, including background scripts, content scripts, and popup UI.
With CRXJS, we eliminated the boilerplate, improved the development speed with HMR, and kept our extension lean and maintainable. It’s the perfect tool for modern Chrome extension development, allowing us to focus on building features instead of wrestling with configuration files.
If you're working with React, Vite, or TypeScript, CRXJS is a no-brainer! 🚀
When developing Classmate - AI-powered quiz assistant, we faced all the classic extension challenges:
✅ Ensuring a seamless user experience with React + Vite.
✅ Handling authentication with Supabase.
✅ Managing API calls efficiently with Zodios.
✅ Avoiding Chrome Web Store rejection by keeping permissions minimal.
Through smart architecture choices and the right stack, we turned a simple idea into a powerful AI assistant for online learners. 🚀
Final Thoughts 💡
Chrome extension development is an exciting playground where you can experiment with browser APIs, automation, and AI. Whether you’re building productivity tools, AI-driven helpers, or something entirely unique, there’s no limit to what you can create.
Got an idea? Time to ship it! 🚢💻