A rewrite in name only

You have a web page that works. It renders, it fetches, it stores a token, it listens for scroll. To ship it inside a mini-program runtime, you do not throw it away — you remap it. The logic mostly survives; the surface it runs on changes. The trap is assuming the browser is still underneath. It is not: there is no document, no window, no localStorage global, and no <div> you can poke at. Here is the map that keeps a migration predictable.

Lifecycle: events become callbacks

On the web you lean on DOMContentLoaded, load, and visibilitychange. A mini-program page is a Page() object whose lifecycle is a set of named callbacks:

Page({
  onLoad(query) { /* once, on first create — read route params here */ },
  onShow() { /* every time the page appears, incl. return from child */ },
  onReady() { /* first render done, safe to query layout */ },
  onHide() { /* backgrounded or navigated away */ },
  onUnload() { /* destroyed — clear timers here */ },
})

The single biggest behavioral difference: onShow fires on return from a child page, so list pages that must reflect a mutation made downstream re-fetch there — not only in onLoad. App-level events (onLaunch, onShow, onHide) live on App(). There is no beforeunload prompt; if you need a guard, intercept the navigation call.

View: HTML and CSS become WXML and WXSS

HTML becomes WXML — a template with {{ }} bindings and no DOM you can traverse. You never document.querySelector and mutate; you call this.setData({ key: value }) and the render layer diffs. That single rule eliminates a whole category of layout bugs but also kills any code that assumed it could reach into the node tree.

WXSS is CSS with two changes that bite: a new unit rpx where 750rpx equals the screen width (so 375px design → 750rpx, auto-scaled per device), and a reduced selector set — no adjacent or attribute selectors, and far fewer pseudo-classes (last-child is gone). Keep styles flat and class-based.

Network: fetch becomes wx.request

This is where most migrations stall. wx.request is not fetch:

ConcernWeb (fetch)Mini-program (wx.request)
TransportHTTP/HTTPS, any hostHTTPS/WSS only, host must be on the allowlist
DomainnoneServer domain whitelist configured in console; localhost/IP blocked
Concurrencybrowser-limitedMax 10 concurrent for request/upload/download
Timeoutper callDefault and max 60s; configurable in app.json
Referrerset by browserFixed platform format, cannot be customized
BackgroundcontinuesIf not finished in 5s after background, fail interrupted

The allowlist is the classic wall: a request that works in DevTools with domain checking off fails in the trial and production builds until the domain is registered and ICP-filed. Wrap wx.request in a Promise layer for headers, token injection, and 401 handling — exactly as you would for fetch, but plan the queue because of the 10-connection cap.

Storage and state

localStorage becomes wx.setStorageSync / wx.getStorageSync, capped at 10MB. sessionStorage has no direct twin — use page-route params or global getApp().globalData for the short-lived bits. Do not assume persistence across a cold start is instant; read async where the UI can tolerate it.

When Web APIs have no twin

Some browser capabilities simply are not there, and the migration plan must name them:

  • Geolocation — navigator.geolocation → wx.getLocation (with scope authorization).
  • Media / live — WebRTC is unavailable; use native <live-pusher> / <live-player> components.
  • Maps, camera, video — native components <map>, <camera>, <video> replace the HTML elements and sit above the WebView layer.
  • Cookies / same-origin — there is no cookie jar shared with a backend the way the browser has; auth travels as tokens in request headers you inject yourself.
  • File system — no arbitrary FS; use wx.getFileSystemManager for the sandbox you are given.

Where Cross Mini App fits

Cross Mini App gives you a runtime that already speaks this surface — wx.* parity, rpx layout, the 10-connection model, and the allowlist semantics — so a ported H5 becomes a catalog entry rather than a re-platforming project. You keep your business logic, swap the browser-shaped edges for runtime-shaped ones, and ship inside a host app that your users already have. The migration is a map, not a cliff.

Sources