Click "Upload", and the developer tool pops up a line of red text: Main package size exceeds the 2MB limit. The version cannot be released, and the requirements are queued up.
This article won't cover a theory overview, but lists methods to compress the main package back under 2MB, ordered by "saves the most, changes the least".
First, figure out: what exactly is the 2MB limit blocking?
WeChat has two hard upper limits for 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 is the size of the uploaded code package, not just JS. All files that will be packaged into the project directory count: .js, .wxml, .wxss, ., fonts, audio, and — usually the biggest chunk — images.
This is a rigid limit that you cannot bypass; it's not just "optimization is better". So, the first step isn't to start coding, but to 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 finer details, use "Code Dependency Analysis" in the toolbar, which lists the size by file and marks files that are not referenced by any page.
Most projects, after looking at this table, will find 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, but an unprocessed banner image could be over a hundred KB, and an images/ directory can easily eat up half the main package.
The following is listed in descending order of benefit.
Method 1: Delete files that are completely useless
The easiest method, and also the most commonly ignored.
- Files marked as unreferenced in "Code Dependency Analysis": Old icons left over from historical versions, deprecated pages, test images — delete them directly.
- Files that shouldn't be in the package: Design mockups, README,
.psd, original 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" }
]
}
}
- Fully importing a component library: It is a common "invisible fat man" to import the entire UI library into the main package when only using three components. Change it to on-demand import and only keep the component directories that are 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 necessary 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: Images and components used by subpackage pages need to 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, no matter how you split the subpackages. preloadRuleSubpackage preloading: When entering the home page, pull down the subpackage that is likely to be used next. Users won't feel any noticeable delay when they click in.- Independent Subpackage (
"independent": true): Suitable for activity pages or landing pages that can be opened independently of the main package, not requiring dependencies on the main package at startup. - Subpackage Asynchrony: When referencing components or JS across subpackages, you can use placeholder components and
require.asyncto avoid bringing code back into the main package just for "sharing".
The cost of subpackages is that you need to modify the directory structure and jump paths. It is a significant amount of work to split an old project, 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 benefit)
Images are the easiest part of the main package to "carry extra fat". Designer-exported PNGs contain redundant data blocks, and JPGs use high quality parameters. Users can't see these bytes, but every single one counts toward the 2MB limit.
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. - Startup page, first-screen Logo, fallback placeholder images: They must be displayable even in weak network or offline conditions.
- Frequently appearing small icons: It's not cost-effective to go through a network request every time.
These images can only stay in the package, so the only way is to make them smaller.
Avoid a pitfall: Background images in wxss
Using background-image to reference local images in .wxss does not work on real devices. A common workaround is to convert to inline base64. However, base64 encoding will inflate the size by about one-third, and it is hidden inside the style file, so it is not very obvious in dependency analysis. If you can change it to an <image> component or a network image, don't inline it; if inline is unavoidable, compress the original image first before converting.
Use ImgZilla to in-place compress an entire resource directory
What developers fear most about compressing images is having to go back and change paths — in wxml, in wxss, in JS configuration, in tabBar configuration, everywhere there is /images/xxx.png. Many compression tools save them as xxx-min.png or require you to export to another folder, requiring manual replacement and re-verification to ensure nothing is missed.
ImgZilla is a macOS image compression tool that does in-place compression:
- Filename, path, and format do not change.
icon-home.pngremainsicon-home.png, and you don't need to change a single line of code referencing it. - Just drag the whole directory in. 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 immediately see the package size change in the developer tools. - Original files are moved to the Trash by default. If you aren't satisfied with a specific image, right-click "Put Back" to restore it. If the project is in Git, you have an extra layer of insurance.
- Runs entirely locally, no network upload. Assets from company projects never leave your computer, and there are no limits on the number of files or individual file size found in online compression sites.
Regarding image quality, the processing is different for different formats. Here is the clarification:
- PNG: Uses oxipng for lossless compression, pixel-for-pixel unchanged. Many icons and sprites in Mini Programs are PNG, so this part can be safely compressed.
- SVG: Cleans up redundant tags, also lossless.
- JPG / WebP / GIF etc.: Belongs to lossy re-encoding, with parameters tuned to the "visually lossless" range — the human eye is hard to distinguish the 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 already optimized: Files with a compression rate of less than 0.4% will be marked as "already minimal" and remain unchanged, so it won't blur images just to make the number look good. So if your images were already seriously optimized before, this step might not save much — which is normal, and suggests you should move on to subpackages.
- It does not change format or resolution. PNG remains PNG, and the size does not change. If you want to convert to WebP or reduce a 3x image to 2x, that is a separate matter requiring 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 have conducted two public tests: a batch of JPGs that had already been compressed once during website entry saved another 46.8%; 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 are usually not seriously compressed, so it is worth running it once to see.
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 swap for a tool that can "preserve the original filename".
Method 4: Move large images to a CDN
After compression, images that are still very large — such as activity banners, long detail page images, or large product images — are not suitable for being in the package anyway. Upload them to object storage or a CDN and change the code to use network addresses, and the main package will immediately be lighter.
But this is not free:
- The first load must go through the network, and there will be a blank period under weak network conditions, so it is best to use placeholder images or skeleton screens.
- You need to maintain a set of upload, caching, and update processes.
- CDN is charged by traffic. Every time an image is loaded, it costs money. Traffic fees are roughly "Image size × Access count". So, before moving to a CDN, it is equally 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 gaps can be squeezed from the code:
- In the developer tools "Details -> Local Settings", check to 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 doing performance optimization, turn it on too. - Check
miniprogram_npm: Are there npm packages in the built output that are fully imported but only using one or two functions? - Consider changing static data written in large blocks in JS (city lists, configuration 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 | Very little | 1 |
| In-place image compression | The more images, the more casual the export, the more saved | Almost zero, paths unchanged | 2 |
| Subpackages | Can be a lot | Directory and routing must be moved | 3 |
| Move large images to CDN | A lot | Need to change references, maintain upload process | 4 |
| Code compression and on-demand import | Less | Depends on situation | 5 |
The logic of the order is simple: do the changes with small impact first, then the changes with large impact. Deleting files and compressing images almost don't touch the business code. After doing them, look at how much space is left in the main package — if it is back under 2MB, today's version can be released; if it's still not enough, then move on to subpackages and CDN, and you'll have a clear picture.
The main package limit is often encountered at the most stressful time before going live. Incorporate image compression into your daily workflow — compress every new image before adding it — and next time you won't have to worry about that red text on the deadline day.
