# Cloudflare Worker Deployment Failure Investigation Complete troubleshooting workflow for "Script exceeds size limit" deployment failure, resolved through bundle optimization and code splitting. ## Overview **Incident**: Worker deployment failing with size limit error **Impact**: Production deployment blocked for 2 hours **Root Cause**: Bundle size grew from 1.2MB to 5.2MB after adding dependencies **Resolution**: Bundle optimization (code splitting, tree shaking) reduced size to 1.8MB **Status**: Resolved ## Incident Timeline | Time | Event | Action | |------|-------|--------| | 14:00 | Deployment initiated via CI/CD | `wrangler deploy` triggered | | 14:02 | Deployment failed | Error: "Script exceeds 1MB size limit" | | 14:05 | Investigation started | Check recent code changes | | 14:15 | Root cause identified | New dependencies increased bundle size | | 14:30 | Fix implemented | Bundle optimization applied | | 14:45 | Fix deployed | Successful deployment to production | | 16:00 | Monitoring complete | Confirmed stable deployment | --- ## Symptoms and Detection ### Initial Error **Deployment Command**: ```bash $ wrangler deploy ✘ [ERROR] Script exceeds the size limit (5.2MB > 1MB after compression) ``` **CI/CD Pipeline Failure**: ```yaml # GitHub Actions output Step: Deploy to Cloudflare Workers ✓ Build completed (5.2MB bundle) ✗ Deployment failed: Script size exceeds limit Error: Workers Free plan limit is 1MB compressed ``` **Impact**: - Production deployment blocked - New features stuck in staging - Team unable to deploy hotfixes --- ## Diagnosis ### Step 1: Check Bundle Size **Before Investigation**: ```bash # Build the worker locally npm run build # Check output size ls -lh dist/ -rw-r--r-- 1 user staff 5.2M Dec 5 14:10 worker.js ``` **Analyze Bundle Composition**: ```bash # Use webpack-bundle-analyzer npm install --save-dev webpack-bundle-analyzer # Add to webpack.config.js const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }; # Build and open analyzer npm run build # Opens http://127.0.0.1:8888 with visual bundle breakdown ``` **Bundle Analyzer Findings**: ``` Total Size: 5.2MB Breakdown: - @anthropic-ai/sdk: 2.1MB (40%) - aws-sdk: 1.8MB (35%) - lodash: 800KB (15%) - moment: 300KB (6%) - application code: 200KB (4%) ``` **Red Flags**: 1. Full `aws-sdk` imported (only needed S3) 2. Entire `lodash` library (only using 3 functions) 3. `moment` included (native Date API would suffice) 4. Large AI SDK (only using text generation) --- ### Step 2: Identify Recent Changes **Git Diff**: ```bash # Check what changed in last deploy git diff HEAD~1 HEAD -- src/ # Key changes: + import { Anthropic } from '@anthropic-ai/sdk'; + import AWS from 'aws-sdk'; + import _ from 'lodash'; + import moment from 'moment'; ``` **PR Analysis**: ``` PR #234: Add AI content generation feature - Added @anthropic-ai/sdk (full SDK) - Added AWS S3 integration (full aws-sdk) - Used lodash for data manipulation - Used moment for date formatting Result: Bundle size increased by 4MB ``` --- ### Step 3: Cloudflare Worker Size Limits **Plan Limits**: ``` Workers Free: 1MB compressed Workers Paid: 10MB compressed Current plan: Workers Free Current size: 5.2MB (over limit) Options: 1. Upgrade to Workers Paid ($5/month) 2. Reduce bundle size to <1MB 3. Split into multiple workers ``` **Decision**: Reduce bundle size (no budget for upgrade) --- ## Resolution ### Fix 1: Tree Shaking with Named Imports **Before** (imports entire libraries): ```typescript // ❌ BAD: Imports full library import _ from 'lodash'; import moment from 'moment'; import AWS from 'aws-sdk'; // Usage: const unique = _.uniq(array); const date = moment().format('YYYY-MM-DD'); const s3 = new AWS.S3(); ``` **After** (imports only needed functions): ```typescript // ✅ GOOD: Named imports enable tree shaking import { uniq, map, filter } from 'lodash-es'; import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; // ✅ BETTER: Native alternatives const unique = [...new Set(array)]; const date = new Date().toISOString().split('T')[0]; // S3 client (v3 - modular) const s3 = new S3Client({ region: 'us-east-1' }); ``` **Size Reduction**: ``` Before: - lodash: 800KB → lodash-es tree-shaken: 50KB (94% reduction) - moment: 300KB → native Date: 0KB (100% reduction) - aws-sdk: 1.8MB → @aws-sdk/client-s3: 200KB (89% reduction) ``` --- ### Fix 2: External Dependencies (Don't Bundle Large SDKs) **Before**: ```typescript // worker.ts - bundled @anthropic-ai/sdk (2.1MB) import { Anthropic } from '@anthropic-ai/sdk'; const client = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY }); ``` **After** (use fetch directly): ```typescript // worker.ts - use native fetch (0KB) async function callAnthropic(prompt: string, env: Env) { const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': env.ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01' }, body: JSON.stringify({ model: 'claude-3-sonnet-20240229', max_tokens: 1024, messages: [ { role: 'user', content: prompt } ] }) }); return response.json(); } ``` **Size Reduction**: ``` Before: @anthropic-ai/sdk: 2.1MB After: Native fetch: 0KB Savings: 2.1MB (100% reduction) ``` --- ### Fix 3: Code Splitting (Async Imports) **Before** (everything bundled): ```typescript // worker.ts import { expensiveFunction } from './expensive-module'; export default { async fetch(request: Request, env: Env) { // Even if not used, expensive-module is in bundle if (request.url.includes('/special')) { return expensiveFunction(request); } return new Response('OK'); } }; ``` **After** (lazy load): ```typescript // worker.ts export default { async fetch(request: Request, env: Env) { if (request.url.includes('/special')) { // Only load when needed (separate chunk) const { expensiveFunction } = await import('./expensive-module'); return expensiveFunction(request); } return new Response('OK'); } }; ``` **Size Reduction**: ``` Main bundle: 1.8MB → 500KB (72% reduction) expensive-module chunk: Loaded on-demand (lazy) ``` --- ### Fix 4: Webpack Configuration Optimization **Updated webpack.config.js**: ```javascript const webpack = require('webpack'); const path = require('path'); module.exports = { entry: './src/worker.ts', target: 'webworker', mode: 'production', optimization: { minimize: true, usedExports: true, // Tree shaking sideEffects: false, }, resolve: { extensions: ['.ts', '.js'], alias: { // Replace heavy libraries with lighter alternatives 'moment': 'date-fns', 'lodash': 'lodash-es' } }, module: { rules: [ { test: /\.ts$/, use: { loader: 'ts-loader', options: { transpileOnly: true, compilerOptions: { module: 'esnext', // Enable tree shaking moduleResolution: 'node' } } }, exclude: /node_modules/ } ] }, plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }) ], output: { filename: 'worker.js', path: path.resolve(__dirname, 'dist'), libraryTarget: 'commonjs2' } }; ``` --- ## Results ### Bundle Size Comparison | Category | Before | After | Reduction | |----------|--------|-------|-----------| | **@anthropic-ai/sdk** | 2.1MB | 0KB (fetch) | -100% | | **aws-sdk** | 1.8MB | 200KB (v3) | -89% | | **lodash** | 800KB | 50KB (tree-shaken) | -94% | | **moment** | 300KB | 0KB (native Date) | -100% | | **Application code** | 200KB | 200KB | 0% | | **TOTAL** | **5.2MB** | **450KB** | **-91%** | **Compressed Size**: - Before: 5.2MB → 1.8MB compressed (over 1MB limit) - After: 450KB → 180KB compressed (under 1MB limit) --- ### Deployment Verification **Successful Deployment**: ```bash $ wrangler deploy ✔ Building... ✔ Validating... Bundle size: 450KB (180KB compressed) ✔ Uploading... ✔ Deployed to production Production URL: https://api.greyhaven.io Worker ID: worker-abc123 ``` **Load Testing**: ```bash # Before optimization (would fail deployment) # Bundle: 5.2MB, deploy: FAIL # After optimization $ ab -n 1000 -c 10 https://api.greyhaven.io/ Requests per second: 1250 [#/sec] Time per request: 8ms [mean] Successful requests: 1000 (100%) Bundle size: 450KB ✓ ``` --- ## Prevention Measures ### 1. CI/CD Bundle Size Check ```yaml # .github/workflows/deploy.yml - Add size validation steps: - run: npm ci && npm run build - name: Check bundle size run: | SIZE_MB=$(stat -f%z dist/worker.js | awk '{print $1/1048576}') if (( $(echo "$SIZE_MB > 1.0" | bc -l) )); then echo "❌ Bundle exceeds 1MB"; exit 1 fi - run: npx wrangler deploy ``` ### 2. Pre-commit Hook ```bash # .git/hooks/pre-commit SIZE_MB=$(stat -f%z dist/worker.js | awk '{print $1/1048576}') [ "$SIZE_MB" -lt "1.0" ] || { echo "❌ Bundle >1MB"; exit 1; } ``` ### 3. PR Template ```markdown ## Bundle Impact - [ ] Bundle size <800KB - [ ] Tree shaking verified Size: [Before → After] ``` ### 4. Automated Analysis ```json { "scripts": { "analyze": "webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json" } } ``` --- ## Lessons Learned ### What Went Well ✅ Identified root cause quickly (bundle analyzer) ✅ Multiple optimization strategies applied ✅ Achieved 91% bundle size reduction ✅ Added automated checks to prevent recurrence ### What Could Be Improved ❌ No bundle size monitoring before incident ❌ Dependencies added without size consideration ❌ No pre-commit checks for bundle size ### Key Takeaways 1. **Always check bundle size** when adding dependencies 2. **Use native APIs** instead of libraries when possible 3. **Tree shaking** requires named imports (not default) 4. **Code splitting** for rarely-used features 5. **External API calls** are lighter than bundling SDKs --- ## Related Documentation - **PlanetScale Issues**: [planetscale-connection-issues.md](planetscale-connection-issues.md) - **Network Debugging**: [distributed-system-debugging.md](distributed-system-debugging.md) - **Performance**: [performance-degradation-analysis.md](performance-degradation-analysis.md) - **Runbooks**: [../reference/troubleshooting-runbooks.md](../reference/troubleshooting-runbooks.md) --- Return to [examples index](INDEX.md)