Click "Upload" and the developer tool pops up a line of red text: Main package size exceeds 2MB limit. The version can't be released, and requirements are still queuing up.
This article doesn't cover a full theory manual, but lists methods to compress the main package back under 2MB, ordered by "saving more, changing less".
First, figure out: what exactly is the 2MB cap?
WeChat has two hard caps on Mini Program code packages:
| Limit | Cap |
|---|---|
| Main package (including app.js, tabBar pages, public resources, etc.) | 2MB |
| Single subpackage | 2MB |
| Total of all packages in the Mini Program | 20MB |
Note that the "size" here refers to the code package size after upload, not just JS. All files in the project directory that will be packed in are counted: .js, .wxml, .wxss, ., fonts, audio, and — usually the biggest chunk — images.
This is a rigid limit that "can't be compressed, so it can't be released"; it's not "optimizing a bit is better". So the first step isn't to act, but to see where the money is being spent first.
Step 0: See what in the main package is taking up space
WeChat Developer Tools' top right "Details → Basic Info" shows the local code package total size; more detailed info is in the toolbar's "Code Dependency Analysis", which lists sizes by file and marks which files are not referenced by any page.
Most projects see the same thing after looking at this table: static resources like images and fonts are much larger than business code. A few tens of thousands of lines of JS is just a few hundred KB, but an unprocessed banner image can be over a hundred KB, and an images/ directory can easily eat up half the main package.
Below is ranked from high to low benefit.
Method 1: Delete files that are completely useless
The easiest and most often ignored step.
- Files marked as unreferenced in "Code Dependency Analysis": Old icons left over from historical versions, abandoned pages, test images, delete them directly.
- Files that shouldn't be in the package: Design drafts, README,
.psd, raw assets, mock data. Move them out of the project directory if possible; if not, usepackOptions.ignoreinproject.config.to exclude them:
{
"packOptions": {
"ignore": [
{ "type": "folder", "value": "design" },
{ "type": "suffix", "value": ".psd" }
]
}
}
- Full import of component libraries: It's a common "invisible fat person" to import the entire UI library into the main package even if only three components are used. Change to on-demand import, keeping only the directories of the components actually used.
Method 2: Subpackages — Move non-first-screen pages out of the main package
This is the official correct solution. Keep only the startup page, tabBar pages, and their truly needed public code in the main package; split the rest of the pages into subpackages based on business logic:
{
"pages": ["pages/index/index", "pages/mine/mine"],
"subPackages": [
{ "root": "packageOrder", "pages": ["list/list", "detail/detail"] },
{ "root": "packageActivity", "pages": ["index/index"] }
],
"preloadRule": {
"pages/index/index": { "network": "all", "packages": ["packageOrder"] }
}
}
A few key points:
- Resources follow the pages: Subpackage pages' own images and components should be put into the subpackage directory. If placed in the main package's public
images/, the volume is still counted against the main package no matter how you split the subpackages. preloadRuleSubpackage preloading: While entering the home page, fetch the subpackage that is likely to be used next. Users barely feel any latency when they click in.- Independent Subpackage (
"independent": true): Suitable for activity pages, landing pages, and other pages that can open independently without depending on the main package. - Subpackage Async: When referencing components or JS across subpackages, use placeholder components and
require.asyncto avoid pulling code back into the main package just for "sharing".
The trade-off is that you have to modify the directory structure and jump paths. Splitting an old project requires significant work, which is why it's worth doing the next step first — often, after doing this, the main package is already back under 2MB.
Method 3: Compress images (smallest change, most direct benefit)
Images are the easiest part of the main package to be "white and fat". PNGs exported by designers come with extra data blocks, and JPGs use high quality parameters. Users can't see these bytes, but every single one counts towards the 2MB.
Which images must stay in the package
Not all images can be moved to CDN:
- tabBar icons:
iconPath/selectedIconPathmust be local paths, network images are not supported. - Startup page, first screen Logo, fallback placeholder images: Must be displayable even on weak or offline networks.
- Frequently appearing small icons: It's not cost-effective to request them via network every time.
These images can only stay in the package, so the only way is to make them smaller.
Avoid a pitfall along the way: background images in wxss
Using background-image in .wxss to reference local images does not work on real devices. A common workaround is to convert to inline base64. However, base64 encoding inflates the size by about one-third, and it's hidden in the style file, not very obvious in dependency analysis. If you can change it to an <image> component or a network image, don't inline; if inline is absolutely necessary, compress the original image first before converting.
Use ImgZilla to compress an entire resource directory in place
The most feared thing about Mini Program development is that after compressing images, you have to go back and change paths — everywhere in wxml, wxss, JS config, and tabBar config. Many compression tools save as xxx-min.png or require exporting to another folder, requiring manual replacement and double-checking for omissions.
ImgZilla is a macOS image compression tool that does in-place compression:
- File name, path, and format remain unchanged.
icon-home.pngstaysicon-home.png; no changes needed in the code. - Just drag in the whole directory. It recursively scans all subdirectories and automatically skips hidden files and
node_modules. Drag inimages/,static/, or even the entire project directory; after compression, just open it in the Developer Tools to see the package size change. - Original images are moved to the trash by default. If you aren't satisfied, right-click "Put back" to restore. If the project is in Git, you get an extra layer of insurance.
- Runs entirely locally, no upload. Project assets won't leave your computer, and there are no limits on the number of images or size per image found on online compression sites.
Regarding image quality, the handling differs by format:
- PNG: Uses oxipng for lossless compression, pixel-perfect. Mini Programs have many icons and slices that are PNG, so this part can be compressed with confidence.
- SVG: Cleans up redundant tags, also lossless.
- JPG / WebP / GIF, etc.: Belongs to lossy re-encoding, with parameters tuned for the visually lossless range — hard for the naked eye to notice a difference. Don't just trust blindly; the built-in comparison window (
⌘D) allows side-by-side comparison and zooming to actual pixels to verify.
Two other points worth knowing:
- It won't force-compress images that are already compressed. Files with a compression rate of less than 0.4% are marked as "minimized" and kept as is; it won't blur images just to make the number look good. So if your images were already seriously optimized, you might not save much here — which is normal, and means it's time to switch to subpackages.
- It doesn't convert formats or change resolution. PNG stays PNG, size unchanged. If you want to switch to WebP or reduce a 3x image to 2x, that's a separate matter that needs separate handling.
How much you can save depends on how the images were originally exported; we won't give a vague percentage. As a reference, we did two public tests: A batch of JPGs already compressed during website ingestion saved another 46.8% after compressing again; a batch of camera-direct JPGs saved 76.8% (see the "ImgZilla Test Series"). Images in Mini Programs are often directly exported from design tools and usually haven't been seriously compressed, so it's worth running it once first.
ImgZilla is currently only available for macOS (macOS 12.3 and above), downloadable from the Mac App Store, with 10 free compressions per day. Students developing on Windows can apply the same logic from this section, just use a tool that also "preserves the original filename".
Method 4: Move large images to CDN
After compressing, images that are still very large — like activity banners, long detail page images, large product images — shouldn't have been in the package in the first place. Upload them to object storage or CDN and switch the code to network addresses; the main package instantly feels lighter.
But this isn't free:
- The first load requires a network request; there will be a blank period on weak networks, so it's best to use placeholder images or skeleton screens.
- You need to maintain a set of upload, caching, and update processes.
- CDN charges by traffic. Every time an image is loaded, it costs money. Traffic fees are roughly "Image Size × Access Count". So before moving to CDN, it's also worth compressing the images first — compress once, and every subsequent access saves traffic.
Method 5: Code-level cleanup
After handling images and structure, the remaining small spaces can be cut from the code:
- In Developer Tools "Details → Local Settings", check to compress scripts, styles, and WXML when uploading.
- In
app., enable"lazyCodeLoading": "requiredComponents"(on-demand injection). It mainly improves startup speed rather than package size, but since you're doing performance optimization, turn it on together. - Check
miniprogram_npm: See if the built npm package has full package imports when only one or two functions are used. - Consider changing static data written in large blocks in JS (city lists, config tables) to API delivery.
Summary Table
| Method | How much can be saved | How much needs to be changed | Suggested Order |
|---|---|---|---|
| Delete useless files | Depends on project history baggage | Very little | 1 |
| In-place image compression | The more images and the more casually exported, the more you save | Almost zero, paths unchanged | 2 |
| Subpackages | Can be a lot | Directory and routing must change | 3 |
| Move large images to CDN | A lot | Need to change references and maintain upload process | 4 |
| Code compression and on-demand import | Less | Depends on situation | 5 |
The logic is simple: do the changes with small impact first, then the ones with large impact. Deleting files and compressing images barely touch business code. After doing that, see how much space is left in the main package — if it's already back under 2MB, the version for today can be released; if not, then move on to subpackages and CDN, and you'll have a clear idea.
Main package limits often happen right before launch, when things are most tense. Put image compression into the daily workflow — compress every new image before adding it — so next time you don't have to worry about that red text right before the deadline.
