React Native Skia runs in the browser via CanvasKit, a WebAssembly (WASM) build of Skia. The CanvasKit WASM file is 2.9MB when gzipped and loads asynchronously, giving you full control over when and how Skia loads.
We support direct integration with Expo and Remotion, plus manual installation for any webpack projects.
React Native Skia can be used on web projects without needing React Native Web.
Expo
Automatic Configuration
For new Expo Router projects, use the Expo Skia template which sets up web support automatically:
yarn create expo-app my-app -e with-skia
# or
npx create-expo-app my-app -e with-skia
This template includes proper web configuration out of the box.
Manual Configuration
Use the setup-skia-web script to ensure canvaskit.wasm is accessible in your Expo project’s public folder:
npx expo install @shopify/react-native-skia
yarn setup-skia-web
Run yarn setup-skia-web each time you upgrade the package. Consider adding it to your postinstall script.
After setup, choose your loading method below.
For Expo Router projects using deferred component registration, create a custom main property in package.json:
Then create index.web.tsx:
import '@expo/metro-runtime';
import { App } from 'expo-router/build/qualified-entry';
import { renderRootComponent } from 'expo-router/build/renderRootComponent';
import { LoadSkiaWeb } from '@shopify/react-native-skia/lib/module/web';
LoadSkiaWeb().then(async () => {
renderRootComponent(App);
});
Remotion
Follow the Remotion installation steps to use React Native Skia with Remotion.
Loading Skia
Ensure Skia is fully loaded before importing the Skia module. Two methods are available:
Using Code-Splitting
The <WithSkiaWeb> component uses code splitting to preload Skia:
import React from 'react';
import { Text } from "react-native";
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
export default function App() {
return (
<WithSkiaWeb
getComponent={() => import("@/components/MySkiaComponent")}
fallback={<Text>Loading Skia...</Text>}
/>
);
}
When using Expo Router in dev mode, you cannot load components inside the app directory as they get evaluated before CanvasKit loads. Place components outside the app directory.
Using Deferred Component Registration
The LoadSkiaWeb() function loads Skia before the React app initiates:
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
import { AppRegistry } from "react-native";
LoadSkiaWeb().then(async () => {
const App = (await import("./src/App")).default;
AppRegistry.registerComponent("Example", () => App);
});
Using a CDN
Load CanvasKit from a CDN. Ensure the CDN-hosted CanvasKit version aligns with React Native Skia’s requirements:
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
import { version } from 'canvaskit-wasm/package.json';
export default function App() {
return (
<WithSkiaWeb
opts={{
locateFile: (file) =>
`https://cdn.jsdelivr.net/npm/canvaskit-wasm@${version}/bin/full/${file}`
}}
getComponent={() => import("./MySkiaComponent")}
/>
);
}
Alternatively with deferred registration:
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
import { version } from 'canvaskit-wasm/package.json';
LoadSkiaWeb({
locateFile: (file) =>
`https://cdn.jsdelivr.net/npm/canvaskit-wasm@${version}/bin/full/${file}`
}).then(async () => {
const App = (await import("./src/App")).default;
AppRegistry.registerComponent("Example", () => App);
});
WebGL Contexts
Web browsers limit WebGL contexts to 16 per webpage. For static canvases without animation values, use the __destroyWebGLContextAfterRender={true} prop:
import { View } from 'react-native';
import { Canvas, Fill } from "@shopify/react-native-skia";
export default function App() {
return (
<View style={{ flex: 1 }}>
{new Array(20).fill(0).map((_, i) => (
<Canvas
key={i}
style={{ width: 100, height: 100 }}
__destroyWebGLContextAfterRender={true}
>
<Fill color="lightblue" />
</Canvas>
))}
</View>
);
}
This works with animated canvases but comes with a performance cost as the context is recreated on each render.
Unsupported Features
The following APIs are currently unsupported on React Native Web:
PathEffectFactory.MakeSum()
PathEffectFactory.MakeCompose()
PathFactory.MakeFromText()
ShaderFilter
Submit a feature request if you need these features.
Manual Webpack Installation
To enable React Native Skia on Web using webpack:
- Ensure
canvaskit.wasm is accessible to the build system
- Configure the build system to resolve
fs and path node modules using node polyfill plugin
- Update aliases for
react-native-reanimated and react-native/Libraries/Image/AssetRegistry
Example webpack v5 configuration:
import fs from "fs";
import { sources } from "webpack";
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
const newConfiguration = {
...currentConfiguration,
plugins: [
...currentConfiguration.plugins,
new (class CopySkiaPlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap("AddSkiaPlugin", (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: "copy-skia",
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
async () => {
const src = require.resolve("canvaskit-wasm/bin/full/canvaskit.wasm");
if (!compilation.getAsset(src)) {
compilation.emitAsset(
"/canvaskit.wasm",
new sources.RawSource(await fs.promises.readFile(src))
);
}
}
);
});
}
})(),
new NodePolyfillPlugin()
],
alias: {
...currentConfiguration.alias,
"react-native-reanimated/package.json": require.resolve(
"react-native-reanimated/package.json"
),
"react-native-reanimated": require.resolve("react-native-reanimated"),
"react-native/Libraries/Image/AssetRegistry": false,
},
};
Then proceed to load Skia.