Click "Upload" and the developer tool pops up a line of red text: Main package size exceeds the 2MB limit. You can't publish the version, and requirements are still queuing up.
This article doesn't cover a theory overview; it simply lists ways to compress the main package back under 2MB, ordered by "saving more, changing less".
First, clarify: what exactly does the 2MB cap restrict?
WeChat has two hard limits on Mini Program code packages:
| Limit | Upper Limit |
|---|---|
| Main package (includes app.js, tabBar pages, public resources, etc.) | 2MB |
| Single subpackage | 2MB |
| Total size of all packages in the Mini Program | 20MB |
Note that the "size" here refers to the uploaded code package size, not just JS. All files that will be packed into the project are counted: .js, .wxml, .wxss, ., fonts, audio, and — usually accounting for the bulk — images.
This is a rigid limit that cannot be bypassed; it's not an "optimization that is better." Therefore, the first step isn't to start changing things, but to clearly see where the money is being spent.
Step 0: See what is taking up space in the main package
In the WeChat Developer Tools, "Details -> Basic Info" in the top right corner shows the total size of the local code package; for more detail, use "Code Dependency Analysis" in the toolbar, which lists file sizes by file and marks files that are not referenced by any page.
Most projects, after looking at this table, discover the same thing: static resources like images and fonts are much larger than business code. Writing tens of thousands of lines of JS is only a few hundred KB, while an unprocessed banner image can be over a hundred KB, and an images/ directory can easily eat up half the main package.
Below, they are ranked from high to low impact.
Method 1: Delete completely useless files
The easiest method, and the one most often overlooked.
- Files marked as unreferenced in "Code Dependency Analysis": Old icons left over from historical versions, deprecated pages, test images — just delete them.
- Files that shouldn't be in the package: Design mockups, README,
.psd, raw assets, Mock data. Move them out of the project directory if possible; if not, exclude them inproject.config.usingpackOptions.ignore:
{
"packOptions": {
"ignore": [
{ "type": "folder", "value": "design" },
{ "type": "suffix", "value": ".psd" }
]
}
}
- Full import of component libraries: It is a common "invisible fatty" to import the entire UI library into the main package when only three components are used. Change this to on-demand import, keeping only the component directories you actually use.
Method 2: Subpackages — Move non-initial-screen pages out of the main package
This is the official solution. Keep only the launch page, tabBar pages, and their necessary common 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"] }
}
}
Key points:
- Resources follow the pages: Images and components used by subpackage pages must be placed inside the subpackage directory. Placing them in a public
images/folder in the main package means the volume is still counted against the main package regardless of how you split the subpackages. preloadRulefor subpackage preloading: When entering the homepage, download the subpackages most likely to be used next. Users barely feel the delay when they click in.- Independent subpackages (
"independent": true): Suitable for activity pages or landing pages that can open independently without depending on the main package. - Asynchronous subpackages: When referencing components or JS across subpackages, you can use placeholder components and
require.asyncto avoid pulling code back into the main package just to "share" it.
The cost of subpackages is that you must change directory structure and jump paths. Splitting an old project requires significant work, which is why it is 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 impact)
Images are the easiest part of the main package to have "wasting space". Designer-exported PNGs come with redundant data blocks, and JPGs use higher quality parameters. These bytes aren't visible to users, but each one counts towards the 2MB.
Which images must stay in the package
Not all images can be moved to a CDN:
- TabBar icons:
iconPath/selectedIconPathmust be local paths and do not support network images. - Launch page, homepage logo, fallback placeholder images: Must be displayable even on weak network or offline.
- Small icons that appear frequently: It's not cost-effective to request them via the network every time.
These images can only stay in the package, so the only method 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. The common workaround is to convert them to base64 inline. However, base64 encoding causes the size to inflate by about one-third. Also, they are hidden inside style files and are not very obvious in dependency analysis. If you can change it to an <image> component or a network image, do not inline it; if you absolutely must, compress the original image first before converting.
Use ImgZilla to compress an entire resource directory in-place
What developers fear most about Mini Programs is having to change paths after compressing images — in wxml, in wxss, in JS configuration, in tabBar configuration, everywhere has /images/xxx.png. Many compression tools save as xxx-min.png or require exporting to another folder, requiring manual replacement and double-checking for missed items.
ImgZilla is a macOS image compression tool that uses in-place compression:
- File names, paths, and formats remain unchanged.
icon-home.pngis stillicon-home.pngafter compression, so no changes are needed in the code. - Just drag in the entire directory. It recursively scans all subdirectories and automatically skips hidden files and
node_modules. Drag inimages/,static/, or even the entire project directory, and you can see the package size change directly in the developer tools. - Original images are moved to the trash by default. If you're not satisfied with a specific one, right-click "Put Back" to restore it. If the project is in Git, this adds an extra layer of security.
- Runs entirely locally, no internet upload. Assets for company projects won't leave your computer, and there are no limits on the number of images or file size like online compression sites have.
Regarding image quality, the handling is different for different formats. Here is the breakdown:
- PNG: Uses lossless compression via oxipng, with pixel-perfect quality. Most icons and slices in Mini Programs are PNG, so this can be compressed with confidence.
- SVG: Cleans up redundant tags; also lossless.
- JPG / WebP / GIF, etc.: Involve lossy re-encoding. Parameters are tuned to the visually lossless range — it is difficult for the naked eye to notice a difference. You don't have to trust blindly; the built-in comparison window (
⌘D) allows side-by-side comparison or zooming in to check pixel-by-pixel.
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 "already minimal" and left as-is. It won't blur images just to make the number look better. So if your images were already seriously optimized, this step might not save much — which is normal and indicates you should move on to subpackages.
- It does not convert formats or change resolution. Compressed PNG remains PNG, and the dimensions stay the same. If you want to convert images to WebP or reduce 3x images to 2x images, that's a separate matter that requires separate handling.
How much you can save depends on how the images were originally exported; we don't give a general percentage. As a reference, we conducted two public tests: for a batch of JPGs that had already been compressed once for website entry, compressing them again saved 46.8%; for a batch of camera-direct JPGs, it 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 on macOS (macOS 12.3 and above). It can be downloaded from the Mac App Store, with 10 free compressions per day. For students using Windows, the logic in this section applies the same; just use a tool that can "preserve the original filename".
Method 4: Move large images to CDN
After compression, if images are still very large — such as activity banners, long detail page images, or product photos — they were never suitable for being in the package in the first place. Upload them to object storage or a CDN and change the code to use network addresses; the main package immediately becomes lighter.
But this isn't free:
- The first load must go through the network, and there will be a blank period on weak networks, so it is best to use placeholders or skeleton screens.
- You need to maintain a set of upload, caching, and update workflows.
- CDN is billed by traffic. Every time an image is loaded, it costs money. Traffic fees are roughly equal to "image size × number of views". So before moving to CDN, it is worth compressing images first — compress once, and every subsequent visit saves traffic.
Method 5: Code-level cleanup
After handling images and structure, you can squeeze out remaining bits from the code:
- In Developer Tools "Details -> Local Settings", check compress scripts, styles, and WXML when uploading.
- In
app., enable"lazyCodeLoading": "requiredComponents"(on-demand injection). This mainly improves startup speed rather than package size, but since you are optimizing performance, enable it together. - Check
miniprogram_npm: Are there npm packages built where the entire package is imported but only one or two functions are used? - Consider changing large chunks of static data written directly in JS (city lists, configuration tables) to be fetched via APIs.
Summary Table
| Method | Potential Savings | Effort Required | Suggested Order |
|---|---|---|---|
| Delete useless files | Depends on project history | Very little | 1 |
| In-place image compression | More images and looser exports = more savings | Almost zero, paths unchanged | 2 |
| Subpackages | Can be significant | Directory and routing changes required | 3 |
| Move large images to CDN | Significant | Change references, maintain upload workflow | 4 |
| Code compression & on-demand import | Less | Depends on situation | 5 |
The logic is simple: do the changes with small impact first, then the larger ones. Deleting files and compressing images hardly touch business code. After doing this, check how much space is left in the main package — if it's already back under 2MB, today's version can be published; if not, then move on to subpackages and CDN, and you'll have a clear picture.
The main package limit often occurs right before the deadline. Incorporate image compression into your daily workflow — compress every new image before adding it — so you don't have to worry about that red text at the deadline next time.
