Skip to main content
  1. Posts/

How to properly shim native dependencies in Expo for web

·3 mins

If you’re building a React Native app with Expo and targeting web, eventually you’ll hit a wall: some native dependencies just don’t work in the browser.

Maybe it’s a crypto library, maybe a native module that doesn’t have a fallback for web, or maybe it’s just a require('fs') that breaks your build. Whatever it is, the solution is usually shimming—but doing it wrong can break your whole routing setup in Expo.

Here’s how to do it correctly, with the most common mistake explained and avoided.

The trap: applying shims too late #

When you’re using expo-router, the router mounts itself automatically from the app/ directory. This works perfectly on mobile. But on web, if you need to shim a native dependency (like using crypto-browserify, stream-browserify, or any custom polyfill), you need to make sure those shims are applied before the router renders anything.

Otherwise? 💥 Boom. You’ll get runtime errors because the shims weren’t applied in time.

The fix: mount the router manually after applying the shims #

To do this, you need to:

  1. Move your router mount into index.ts manually.
  2. Apply your shims first.
  3. Then render the router.

Let’s walk through the fix step by step.


1. Create a custom index.ts entry point #

This replaces the default auto-boot behavior.

index.ts

1
2
3
4
5
6
// Step 1: Shim native deps
import { applyShims } from './shims';
applyShims();

// Step 2: Initialize the router
import "expo-router/entry";

2. Create the shim logic #

You can organize this however you want. Just make sure it’s executed before any React or Router-related code.

shims.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export function applyShims() {
  // Example: Shim Node.js crypto API
  if (typeof window !== 'undefined') {
    globalThis.process = require('process/browser');
    globalThis.Buffer = require('buffer').Buffer;

    // Shim common modules
    require('stream-browserify');
    require('crypto-browserify');
  }
}

Use npm install process buffer crypto-browserify stream-browserify if needed.


3. Update your tsconfig.json and webpack.config.js if needed #

Depending on how you set up Expo for web, you might also need to alias modules.

Example in your webpack.config.js (if using custom config):

1
2
3
4
5
6
7
config.resolve.fallback = {
  ...config.resolve.fallback,
  crypto: require.resolve('crypto-browserify'),
  stream: require.resolve('stream-browserify'),
  buffer: require.resolve('buffer'),
  process: require.resolve('process/browser'),
};

4. Ensure your package.json main field points to index #

This is crucial! When you create a custom index.ts, you need to make sure your package.json knows about it.

package.json

1
2
3
4
5
6
{
  "name": "your-expo-app",
  "main": "index",
  "version": "1.0.0",
  // ... rest of your config
}

If you don’t set this, Expo might still try to use the default entry point and skip your shims entirely.


Final thoughts #

Don’t let native dependencies block your Expo web deployment. With a proper shim setup and a controlled index.ts, you can run things like crypto, streams, and more — without breaking your routing.

And remember: if you don’t control the order of execution, your shims won’t matter.

Expo is fantastic, but sometimes you’ve got to do a bit of manual wiring to get full platform parity.

Now go shim something.