npm Registry Mirror Setup for China: pnpm, Yarn, Bun

Blog / Developer tools

npm Registry Mirror Setup for China: pnpm, Yarn, Bun

The most widely used npm mirror in China is https://registry.npmmirror.com, and the official registry is https://registry.npmjs.org. The old registry.npm.taobao.org was shut down long ago, so if it is still in your config, replace it soon. Switching registries only changes where package metadata and tarballs are downloaded from. The other half of slow installs is usually binary downloads, lockfiles, and your local network, and we cover those separately below.

First, which registry are you actually using?

Check the current state before changing anything. Many "I switched and it's still slow" cases turn out to be a config overridden somewhere else.

npm config get registry
npm config list            # shows which config file each setting comes from
pnpm config get registry
yarn config get registry          # yarn v1
yarn config get npmRegistryServer # yarn Berry (v2+)

npm config precedence, from highest to lowest: command-line flags > environment variables (NPM_CONFIG_REGISTRY / npm_config_registry) > the project .npmrc > the user-level ~/.npmrc > the global config. If CI sets an environment variable, nothing you write in the project .npmrc takes effect.

While you are at it, check for leftovers of the old domain:

grep -rn "npm.taobao.org" ~/.npmrc ~/.yarnrc ~/.yarnrc.yml .npmrc .yarnrc .yarnrc.yml \
  package-lock.json yarn.lock 2>/dev/null

How do you switch registries in each package manager?

Each tool gets three forms: global (only affects your machine), project-level (travels with the repo, so the team stays consistent), and one-off (a single command).

npm

# Global: writes to ~/.npmrc
npm config set registry https://registry.npmmirror.com

# Project-level: create .npmrc in the project root
echo "registry=https://registry.npmmirror.com" > .npmrc

# One-off
npm install --registry=https://registry.npmmirror.com

pnpm

pnpm reads the same .npmrc, so the project-level npm approach above works for pnpm too.

pnpm config set registry https://registry.npmmirror.com   # global
pnpm install --registry=https://registry.npmmirror.com    # one-off

yarn v1 (Classic)

yarn v1 reads .npmrc and also has its own .yarnrc.

yarn config set registry https://registry.npmmirror.com   # global, writes to ~/.yarnrc
echo 'registry "https://registry.npmmirror.com"' > .yarnrc  # project-level
yarn install --registry https://registry.npmmirror.com     # one-off

yarn Berry (v2 and later)

Berry does not read .npmrc, and the setting has a different name: npmRegistryServer.

# Project-level: writes to .yarnrc.yml in the current directory
yarn config set npmRegistryServer https://registry.npmmirror.com

# Global: writes to ~/.yarnrc.yml
yarn config set -H npmRegistryServer https://registry.npmmirror.com

# One-off: use an environment variable
YARN_NPM_REGISTRY_SERVER=https://registry.npmmirror.com yarn install

bun

bun recommends bunfig.toml: put it in the repo root for project-level config, or at ~/.bunfig.toml for global config.

[install]
registry = "https://registry.npmmirror.com"

Newer versions of bun also read registry from .npmrc and honor the NPM_CONFIG_REGISTRY environment variable. If some people on your team use npm and others use bun, a project-level .npmrc is the easiest shared config.

Quick reference

ToolProject-level config fileSettingOne-off
npm.npmrcregistry--registry=
pnpm.npmrcregistry--registry=
yarn v1.yarnrc or .npmrcregistry--registry
yarn Berry.yarnrc.ymlnpmRegistryServerYARN_NPM_REGISTRY_SERVER=
bunbunfig.toml or .npmrcinstall.registryNPM_CONFIG_REGISTRY=

How do you switch back to the official registry for publishing or the latest versions?

The mirror syncs from the official registry, so a newly published version may take a while to show up. Also, npm publish and npm login must go to the official registry, because the mirror does not accept publishes.

# Switch back permanently
npm config set registry https://registry.npmjs.org
yarn config set npmRegistryServer https://registry.npmjs.org   # Berry

# Specify it only when publishing
npm publish --registry=https://registry.npmjs.org

If your company runs a private registry, you can point just one scope at it without affecting other packages:

# .npmrc
registry=https://registry.npmmirror.com
@your-company:registry=https://npm.your-company.example/

The private registry URL here is only an example.

Why does CI still hit the old address after you switched?

The cause is usually the lockfile. Different lockfiles record download URLs in different ways:

  • package-lock.json (npm): every package has a full resolved URL. Newer npm versions have a replace-registry-host setting whose default is npmjs, meaning npm only swaps registry.npmjs.org for the registry you currently configured. If the lockfile contains npmmirror or the old Taobao domain, npm will not replace it, so even if CI switches back to the official registry it still requests that address, and fails outright if the old domain is dead.
  • yarn.lock (v1): also contains full resolved URLs, and the old domain has to be replaced by hand.
  • yarn Berry's yarn.lock and pnpm-lock.yaml: for packages on the default registry they generally record only the version and integrity hash, not the full URL, so they are barely affected.
  • bun.lock: packages from the default registry usually do not have full URLs recorded. If in doubt, check with grep.

There are three fixes, ordered from smallest to largest change:

# 1. Have npm replace the host in every resolved URL, for this run only
npm ci --replace-registry-host=always

# 2. Replace the old domain in the lockfile with the official registry (check the diff before committing)
sed -i 's#https://registry.npm.taobao.org#https://registry.npmjs.org#g' package-lock.json

# 3. Delete the lockfile and regenerate it (this also upgrades dependencies, so use with care)

Our recommendation: keep the official registry URL in the lockfile, and let each person's machine and CI choose the actual registry through .npmrc or environment variables. That way the lockfile does not churn with personal habits, and switching registries does not require touching the repo. The integrity hash is independent of the download URL, so as long as the tarball downloaded from the mirror has identical content, verification passes.

Why are electron, sharp, and playwright still slow after switching?

The npm packages themselves are tiny. The big downloads happen in the postinstall step, from a different domain: electron pulls binaries from GitHub Releases, playwright and puppeteer download browsers, and old versions of node-sass download prebuilt bindings. Changing the registry does not redirect any of these downloads.

To tell whether this is your problem, watch which step the install log stalls on.

npm install --foreground-scripts --loglevel=http

If it ends up stuck in some package's postinstall, and the log shows github.com, playwright, or storage-related domains, the slow part is the binary download, not the registry.

The usual fix is to point the download URL at a mirror through environment variables. npmmirror provides a binary mirror directory (https://npmmirror.com/mirrors/); use whatever paths actually exist in that directory:

export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
export PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright"
export SASS_BINARY_SITE="https://npmmirror.com/mirrors/node-sass"

A few things to keep in mind:

  • Recent sharp versions publish their prebuilt files as @img/sharp-* npm packages, which go through the registry mirror along with everything else, so you generally do not need a binary URL. Older versions do.
  • Many old tutorials put keys like electron_mirror=... in .npmrc. Newer npm warns about config keys it does not recognize, and says they will stop being supported in a future major version. Environment variables or the CI env block are safer.
  • Newer puppeteer versions use different download variable names than before; follow its official documentation.
  • If CI does not need a browser, use the tool's "skip download" variable to drop this step entirely.

Where is it slow? A diagnostic flow

Switching registries solves the problem of being far from the origin server. If the slowness is in DNS, a proxy, or your local network, a new registry will not help. Work through the layers in this order:

Step 1: measure connection time to the registry and to the binary download domains separately.

for u in https://registry.npmjs.org/react \
         https://registry.npmmirror.com/react \
         https://github.com; do
  curl -o /dev/null -s -w "$u\n  dns=%{time_namelookup}s connect=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n" "$u"
done

These timings are cumulative: dns is name resolution, connect is when the TCP connection is established, tls is when the handshake completes, and ttfb is when the server returns the first byte. This breakdown matches how the cdnping online speed test works: the home page measures public frontend CDNs for "first connection" (including DNS and the TLS handshake) and "round trip" (the median of 3 requests over a reused connection). Run it in your browser for a quick view of the rough gap between your network and CDN nodes inside and outside China. For cross-CDN comparisons see the CDN speed ranking, and for how we measure see methodology. For what each of these timings means, see AI API latency breakdown; the principle is the same as for package installs.

Step 2: match the result to a fix.

SymptomLikely causeFix
dns alone takes several secondsSlow or poisoned DNS resolutionChange DNS; see the DNS over HTTPS setup guide
dns is normal, connect is highLong route or packet loss to the originSwitch to a mirror
Mirror ttfb is normal but install is still slowStuck on binary downloadsConfigure binary mirrors as in the previous section
Sometimes fast, sometimes slow, some requests time outProxy rules or IPv6 egress mismatchCheck traffic routing and dual-stack
Both the official registry and the mirror are slowYour local network itself has a problemRun an overall network self-check first

Step 3: check your proxy. npm reads the proxy and https-proxy settings and the HTTPS_PROXY environment variable. With a global proxy on, traffic to a China-based mirror may detour overseas and come back, which makes it slower. Use npm config get https-proxy and env | grep -i proxy to look for leftovers. To judge whether your routing rules are working, see How Chinese websites see your real IP and how split tunneling works.

If your system has both IPv4 and IPv6 but the proxy only handles one of them, you get "the same command is sometimes fast and sometimes times out." For troubleshooting, see IPv6 proxy not taking effect.

Step 4: still unclear? Run a full-path check. The developer network self-check list (2026 edition) strings together egress IP, DNS, traffic routing, and speed testing in one sequence, and is worth running on a new machine or a new CI runner.

Checklist before and after switching

  • npm config list has no npm.taobao.org
  • The project .npmrc, .yarnrc.yml, or bunfig.toml matches what the team agreed on
  • The lockfile has no old domains; in npm projects, resolved consistently uses the official registry
  • CI environment variables do not override the project config (or the override is intentional)
  • The publish workflow explicitly passes --registry=https://registry.npmjs.org
  • Binary download URLs for electron, playwright, and similar are in environment variables, not in .npmrc
  • You have used curl to measure connection time to the registry and the binary domains, so you know which layer is slow

Summary

Changing the mirror itself takes one line. If installs are still slow afterward, first use curl or the online speed test to split out DNS, connection, and first-byte time, then use the table above to decide whether to change the registry, configure binary mirrors, or check your proxy and DNS.