Building your Chrome extension for production involves optimization, testing, and packaging. This guide covers best practices for creating production-ready builds.
Production Build
Create a production build:
This command:
- Type-checks your code (if using TypeScript)
- Bundles and optimizes all assets
- Generates the extension in
dist/ directory
- Creates source maps (if configured)
- Minifies JavaScript and CSS
Build Configuration
Configure your production build in vite.config.ts:
import path from 'node:path'
import { crx } from '@crxjs/vite-plugin'
import { defineConfig } from 'vite'
import manifest from './manifest.config'
export default defineConfig({
plugins: [
crx({ manifest }),
],
build: {
// Output directory
outDir: 'dist',
// Generate source maps for debugging
sourcemap: true,
// Minification
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true,
},
},
// Chunk size warnings
chunkSizeWarningLimit: 1000,
// Rollup options
rollupOptions: {
output: {
manualChunks: {
// Split vendor code
vendor: ['react', 'react-dom'],
},
},
},
},
})
Manifest Configuration
Configure your manifest for production:
import { defineManifest } from '@crxjs/vite-plugin'
import pkg from './package.json'
const isDev = process.env.NODE_ENV === 'development'
export default defineManifest({
manifest_version: 3,
// Add [DEV] prefix in development
name: isDev ? `[DEV] ${pkg.name}` : pkg.name,
// Semantic versioning
version: pkg.version,
description: pkg.description,
icons: {
16: 'public/icon-16.png',
48: 'public/icon-48.png',
128: 'public/icon-128.png',
},
action: {
default_popup: 'src/popup/index.html',
},
background: {
service_worker: 'src/background.ts',
type: 'module',
},
// Only include dev permissions in development
permissions: [
'storage',
...(isDev ? ['tabs'] : []),
],
content_scripts: [{
js: ['src/content/main.ts'],
matches: ['https://*/*'],
}],
})
Package.json Scripts
Set up comprehensive build scripts:
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build:watch": "vite build --watch",
"type-check": "tsc -b --noEmit",
"lint": "eslint src",
"preview": "vite preview"
}
}
Optimization Strategies
Code Splitting
Split your code into smaller chunks:
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
// Vendor chunk
if (id.includes('node_modules')) {
return 'vendor'
}
// UI framework chunk
if (id.includes('react') || id.includes('vue') || id.includes('svelte')) {
return 'framework'
}
// Components chunk
if (id.includes('/components/')) {
return 'components'
}
},
},
},
},
})
Tree Shaking
Ensure proper tree shaking:
// Good: Named imports enable tree shaking
import { map, filter } from 'lodash-es'
// Bad: Imports entire library
import _ from 'lodash'
Lazy Loading
Load components on demand:
// React
const Settings = lazy(() => import('./components/Settings'))
// Vue
const Settings = defineAsyncComponent(() => import('./components/Settings.vue'))
// Svelte
const Settings = () => import('./components/Settings.svelte')
Asset Optimization
Optimize images and fonts:
npm install -D vite-plugin-image-optimizer
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
export default defineConfig({
plugins: [
crx({ manifest }),
ViteImageOptimizer({
png: { quality: 80 },
jpeg: { quality: 80 },
webp: { quality: 80 },
}),
],
})
Source Maps
Configure source maps for debugging:
export default defineConfig({
build: {
// 'hidden' generates source maps but doesn't reference them
// Good for production debugging without exposing source
sourcemap: 'hidden',
// Alternative options:
// sourcemap: true, // Full source maps
// sourcemap: false, // No source maps
// sourcemap: 'inline', // Inline source maps
},
})
Chrome Web Store allows source maps. Include them for easier debugging of production issues.
Packaging
Create ZIP Archive
Install the zip plugin:
npm install -D vite-plugin-zip-pack
import zip from 'vite-plugin-zip-pack'
import { name, version } from './package.json'
export default defineConfig({
plugins: [
crx({ manifest }),
zip({
outDir: 'release',
outFileName: `${name}-${version}.zip`,
}),
],
})
After building, find your extension in release/.
Manual Packaging
Or package manually:
# Build first
npm run build
# Create zip
cd dist
zip -r ../extension.zip .
cd ..
Version Management
Use semantic versioning:
{
"version": "1.2.3",
"scripts": {
"version:patch": "npm version patch && npm run build",
"version:minor": "npm version minor && npm run build",
"version:major": "npm version major && npm run build"
}
}
Update version:
npm run version:patch # 1.2.3 -> 1.2.4
npm run version:minor # 1.2.4 -> 1.3.0
npm run version:major # 1.3.0 -> 2.0.0
Build Checklist
Before publishing, verify:
Testing Production Build
Load and test your production build:
-
Build the extension:
-
Open Chrome and navigate to
chrome://extensions
-
Enable “Developer mode”
-
Click “Load unpacked”
-
Select the
dist/ directory
-
Test all features thoroughly
Size Analysis
Analyze bundle size:
npm install -D rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
crx({ manifest }),
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
}),
],
})
After building, opens a visual report of your bundle.
Chrome Web Store Requirements
Size Limits
- Maximum upload size: 20 MB
- Maximum unpacked size: 100 MB
Required Assets
Prepare store assets:
- Small icon: 128x128 PNG
- Promotional images:
- Small: 440x280 PNG
- Marquee: 1400x560 PNG (optional)
- Screenshots: 1280x800 or 640x400 PNG (min 1, max 5)
- Description (max 132 characters)
- Detailed description
- Privacy policy URL (if collecting data)
Manifest Requirements
export default defineManifest({
manifest_version: 3,
name: 'Your Extension Name',
version: '1.0.0',
description: 'Clear, concise description under 132 characters',
icons: {
16: 'public/icon-16.png',
48: 'public/icon-48.png',
128: 'public/icon-128.png',
},
// ... rest of manifest
})
Continuous Integration
Automate builds with GitHub Actions:
.github/workflows/build.yml
name: Build Extension
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run type-check
- name: Lint
run: npm run lint
- name: Build
run: npm run build
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: extension
path: dist/
Environment-Specific Builds
Build for different environments:
{
"scripts": {
"build": "vite build",
"build:staging": "vite build --mode staging",
"build:production": "vite build --mode production"
}
}
VITE_API_URL=https://staging.api.example.com
VITE_API_URL=https://api.example.com
Publishing
Steps to publish:
- Create a Chrome Web Store Developer account
- Pay one-time $5 registration fee
- Build and package your extension
- Upload ZIP file to dashboard
- Fill in store listing details
- Submit for review
Review typically takes 1-3 business days.
Post-Publication
Monitoring
Monitor your extension:
- Check Chrome Web Store reviews
- Monitor error reports
- Track user count and ratings
- Review crash reports (if using error tracking)
Updates
To update:
- Increment version in
package.json
- Build and test
- Upload new ZIP to Chrome Web Store
- Submit for review
Users receive updates automatically.
Next Steps