Web Support
React Native Skia runs in the browser via CanvasKit, a WebAssembly (WASM) build of Skia. The CanvasKit WASM file, which is 2.9MB when gzipped, is loaded asynchronously. Despite its considerable size, it offers flexibility in determining when and how Skia loads, giving you full control over the user experience.
We support direct integration with Expo and Remotion. Additionally, you'll find manual installation steps for any webpack projects.
It should also be mentionned that React Native Skia can be used on projects without the need to install React Native Web.
Expo
Automatic configuration
For new Expo Router projects, we recommend using 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 the proper configuration for web support out of the box, so you don't need to manually configure loading methods.
Manual configuration
Use the setup-skia-web script to ensure that the canvaskit.wasm file is accessible within your Expo project's public folder.
If you're loading CanvasKit from a CDN, running the setup-skia-web script is unnecessary.
$ npx expo install @shopify/react-native-skia
$ yarn setup-skia-web
Run yarn setup-skia-web each time you upgrade the @shopify/react-native-skia package.
Consider incorporating it into your postinstall script for convenience.
After setup, choose your method to Load Skia.
For existing projects using Expo Router, you can use code-splitting or deferred component registration.
If you wish to use deferred component registration with Expo Router, you need to create your own main property in package.json.
For instance, if you've created index.tsx and index.web.tsx in your root directory, update your package.json accordingly:
- "main": "expo-router/entry",
+ "main": "index",
Below is an example of 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);
});
For the index.tsx file, directly invoke renderRootComponent(App).
Remotion
Follow these installation steps to use React Native Skia with Remotion.
Loading Skia
Ensure Skia is fully loaded and initialized before importing the Skia module. Two methods facilitate Skia's loading:
<WithSkiaWeb />for code-splitting, delaying the loading of Skia-importing components.LoadSkiaWeb()to defer root component registration until Skia loads.
Using Code-Splitting
The <WithSkiaWeb> component utilizes code splitting to preload Skia.
The following example demonstrates preloading Skia before rendering the MySkiaComponent:
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
// import() uses the default export of MySkiaComponent.tsx
getComponent={() => import("@/components/MySkiaComponent")}
fallback={<Text>Loading Skia...</Text>}
/>
);
}
When using expo router in dev mode you cannot load components that are inside the app directory, as they will get evaluated by the router before CanvasKit is loaded. Make sure the component to load lies outside the 'app' directory.
Using Deferred Component Registration
The LoadSkiaWeb() function facilitates Skia's loading prior to the React app's initiation.
Below is an index.web.js example:
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
LoadSkiaWeb().then(async () => {
const App = (await import("./src/App")).default;
AppRegistry.registerComponent("Example", () => App);
});
Using a CDN
Below, CanvasKit loads via code-splitting from a CDN. It is critical that 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, use deferred component 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 Contextes
Web browsers limit the number of WebGL contexts to 16 per webpage. Usually developers will see this error when they exceed this limit:
WARNING: Too many active WebGL contexts. Oldest context will be lost.
If you canvas is static and doesn't contain animation values, you can use the __destroyWebGLContextAfterRender={true} prop on your Canvas components to destroy the WebGL context after rendering.
This even works with animated canvases but it will come with a performance cost as the context will be recreated on each render.
import { View } from 'react-native';
import { Canvas, Fill } from "@shopify/react-native-skia";
export default function App() {
return (
<View style={{ flex: 1 }}>
{
// 20 Skia Canvases with __destroyWebGLContextAfterRender={true}
new Array(20).fill(0).map((_, i) => (
<Canvas key={i} style={{ width: 100, height: 100 }} __destroyWebGLContextAfterRender={true}>
<Fill color="lightblue" />
</Canvas>
))
}
</View>
);
}
Unsupported Features
The following React Native Skia APIs are currently unsupported on React Native Web. To request these features, please submit a feature request on GitHub.
Unsupported
PathEffectFactory.MakeSum()PathEffectFactory.MakeCompose()PathFactory.MakeFromText()ShaderFilter
Manual webpack Installation
To enable React Native Skia on Web using webpack, three key actions are required:
- Ensure the
canvaskit.wasmfile is accessible to the build system. - Configure the build system to resolve the
fsandpathnode modules, achievable via the node polyfill plugin. - Update aliases for
react-native-reanimatedandreact-native/Libraries/Image/AssetRegistryso webpack can do the bundle.
Here is an example webpack v5 configuration accommodating React Native Skia:
import fs from "fs";
import { sources } from "webpack";
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
const newConfiguration = {
...currentConfiguration,
plugins: [
...currentConfiguration.plugins,
// 1. Ensure wasm file availability
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)));
}
}
);
});
}
})(),
// 2. Polyfill fs and path modules
new NodePolyfillPlugin()
],
alias: {
...currentConfiguration.alias,
// 3. Suppress reanimated module warning
// This assumes Reanimated is installed, if not you can use false.
"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,
},
}
Finally, proceed to load Skia.