Skip to main content
Supported on
Snapchat

Platform requirements

A Web Lens is a zip of a static web build. Snapchat unpacks it and serves index.html. This page lists the rules that zip must follow. Check it before every upload; most failed submissions trace back to one of these points.

The size limit below reflects the current beta. Snap is confirming the final value before public release, and this page updates if it changes.

Entry point​

index.html must sit at the root of the zip, not inside a folder, and must be no larger than 1 MB (see Size limits). Snapchat loads that file directly and follows its references to reach everything else.

Correct:

my-web-lens.zip
├── index.html
└── a/
├── game.js
└── index.css

Incorrect, because the zip contains the build folder rather than its contents. This is what you get by compressing the dist folder itself instead of selecting the files inside it:

my-web-lens.zip
└── dist/
├── index.html
└── a/

Relative asset paths​

The Lens loads from a URL you don't control, and that URL can differ between the review build and the published Lens. Every reference in index.html and in your scripts must be relative: ./a/game.js, never /a/game.js.

With Vite, set base: './' in vite.config.js. With other bundlers, look for the matching public-path setting. Verify after building:

grep -E 'src="/|href="/' dist/index.html

If that prints anything, the build contains absolute paths and the Lens shows a blank screen.

Runtime fetches follow the same rule: fetch('./data/levels.json'), not fetch('/data/levels.json').

Zip path separators​

Zip entries must use forward slashes: a/game.js. The ZIP specification requires it, and Snapchat's unpacker follows the specification.

Zipping by hand is safe. Windows Explorer's Compress to ZIP file command writes forward slashes, and so does zip -r on macOS and Linux.

The problem is scripting the zip on Windows. PowerShell's Compress-Archive and the .NET Framework's ZipFile class both write backslashes, so they store an entry as a\game.js. An unpacker that follows the specification then treats that as a single filename with a backslash in it, so index.html asks for ./a/game.js and finds nothing. If you automate packaging on Windows, pack with Node instead, so the result is identical on every machine. Install archiver as a development dependency:

npm install --save-dev archiver@8

Then add tools/pack.mjs:

// tools/pack.mjs — zips the contents of dist/ into my-web-lens.zip.
// Run after `npm run build`: node tools/pack.mjs
import { createWriteStream } from 'node:fs';
import { ZipArchive } from 'archiver';

const output = createWriteStream('my-web-lens.zip');
const zip = new ZipArchive({ zlib: { level: 9 } });

zip.pipe(output);
// `false` puts the folder's contents at the zip root instead of under dist/.
zip.directory('dist/', false);
await zip.finalize();

Verify the entry names regardless of how you zipped:

unzip -l my-web-lens.zip

Every nested path in the listing should contain / and no \.

Size limits​

The zip must be 8 MB or smaller. The limit applies to the compressed upload.

index.html itself must be 1 MB or smaller. Keep it a thin entry point that references your scripts, styles, and assets as separate files. Don't inline the whole bundle into it, and don't embed large data: URLs in it. A typical Vite build produces an index.html of a few kilobytes.

That's enough for a hypercasual game, but treat it as a ceiling rather than a target. A smaller zip loads faster, and time to first play is the strongest lever you have on how a Lens performs with its audience. Cheap wins:

  • Don't ship source maps. Set build.sourcemap: false in Vite, which is the default.
  • Pack sound effects into one audio file and play slices of it.
  • Compress textures, and pack your meshes into one file.
  • Import only the parts of your engine you use, so the bundler can drop the rest.

Checklist​

Before every upload:

  • index.html is at the root of the zip.
  • Every path in index.html and in runtime fetches starts with ./.
  • unzip -l shows forward slashes in every entry.
  • The zip is 8 MB or smaller, and index.html is 1 MB or smaller.
  • You tested the build on a phone in portrait, not only in a desktop browser.
Was this page helpful?
Yes
No