I was swapping the tab-bar icons in a wallpaper app for a set of thin, custom SVGs. They looked perfect in Expo Go, so I built a development build with confidence — and on the very same screen, the icons had quietly disappeared. No error. Just a transparent hole where a shape should have been. It took me the better part of an hour to trace it, so let me lay out what was going on for anyone stuck at the same spot.
The symptoms
Anything involving SVG icons tends to fail in one of these shapes:
- Renders in Expo Go, but draws nothing in a development or production build
- A red screen reading
Unable to resolve module ./icon.svg - Renders, but the color stays black and never follows dark mode
- Shows on iOS but not Android, or the other way around
The tricky part is that it usually fails silently as a transparent shape rather than an error. The layout box is still reserved, so the icon is invisible while occupying space — which sent me chasing CSS-style problems for far too long.
How to reproduce it
This happens with code Rork generated, or code you added yourself, that loads an SVG file like this:
import TabIcon from "../assets/icons/home.svg";
export function HomeTab() {
return <TabIcon width={24} height={24} />;
}It looks completely reasonable. But out of the box, the React Native / Expo Metro bundler treats .svg as an image asset. That means <TabIcon /> is an image-source object, not a component, so rendering it as a component produces nothing.
When it happens to work in Expo Go, that is usually an environment-specific accident — a stale cache, an old bundle, a different load path — and leaning on it will bite you in production.
Why the icon doesn't show
There are two root causes.
The first: there is no mechanism to import .svg as a component. React Native has no equivalent of the web's <img src="...svg">; drawing an SVG requires react-native-svg. To treat a .svg file directly as a JSX component, you also need react-native-svg-transformer wired into Metro. Without it, Metro keeps handling SVGs as static assets.
The second: the color is baked into the SVG file. Design tools often export SVGs with concrete fills like fill="#111111", which leaves your app unable to recolor them per theme. That black-that-stays-black-in-dark-mode look is exactly this case.
Fix 1: let Metro import .svg as a component
Install the packages first. Under Expo, npx expo install pulls versions matched to your SDK.
npx expo install react-native-svg
npm install --save-dev react-native-svg-transformerThen edit metro.config.js at the project root. With Expo, building on top of getDefaultConfig is the safe path. Create the file if it doesn't exist yet.
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
// Let SVGs be treated as JSX components
config.transformer.babelTransformerPath = require.resolve(
"react-native-svg-transformer"
);
// Move .svg out of "assets" and into "source code" resolution
config.resolver.assetExts = config.resolver.assetExts.filter(
(ext) => ext !== "svg"
);
config.resolver.sourceExts = [...config.resolver.sourceExts, "svg"];
module.exports = config;The key is removing svg from assetExts and adding it to sourceExts. Skip these two lines and Metro will keep treating SVGs as images even with the transformer installed.
If you use TypeScript, you'll get red squiggles without a type for importing .svg, so add one declaration file.
// declarations.d.ts
declare module "*.svg" {
import type { SvgProps } from "react-native-svg";
const content: React.FC<SvgProps>;
export default content;
}Finally, after changing config, clear the Metro cache and restart. Skip this and an old resolution lingers, making a fixed setup look broken.
npx expo start --clearNow that same import TabIcon from "../assets/icons/home.svg" renders directly as a component.
Fix 2: follow the theme with currentColor
If it renders but the color never changes, replace the SVG's fixed fill with currentColor. react-native-svg-transformer bridges currentColor inside the SVG to the color prop you pass the component.
If your exported path looks like this,
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#111111" d="M12 3l9 7v11h-6v-6H9v6H3V10z" />
</svg>swap the fixed color for currentColor:
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M12 3l9 7v11h-6v-6H9v6H3V10z" />
</svg>On the call site, just pass color and it switches with your theme.
import { useColorScheme } from "react-native";
import HomeIcon from "../assets/icons/home.svg";
export function HomeTab() {
const scheme = useColorScheme();
const tint = scheme === "dark" ? "#F2F2F7" : "#1C1C1E";
return <HomeIcon width={24} height={24} color={tint} />;
}For multi-color illustrations, set only the parts you want to recolor to currentColor and leave accent colors fixed. For single-color icons, unifying every path to currentColor means a palette change later happens in one place.
Which approach to choose
| Situation | Best fit |
|---|---|
| Many designer-exported SVGs | Fix 1 (import via transformer) |
| A few icons, or shapes that change dynamically | Hand-write with Svg / Path from react-native-svg |
| Colors should follow the theme | Add Fix 2 (currentColor) |
I settled on loading small tab-bar and list-row icons as .svg via the transformer, with colors unified to currentColor. Swapping a design becomes a matter of replacing the .svg file, which keeps day-to-day maintenance very light.
Prevention
To avoid the same trap, I now lock down three things up front.
First, add metro.config.js and the type declaration on day one of a project. Adding them later steals time on "why won't this show" investigations.
Second, whenever I add an SVG, I check it once in a development build (not Expo Go). Expo Go can behave differently because of its bundled libraries, so working there is no guarantee it works in production.
Third, I made --clear a reflex after touching config. Most "I fixed it but nothing changed" moments around SVG came down to Metro holding a stale resolution.
References
- react-native-svg (official repository)
- react-native-svg-transformer (official repository)
- Expo: Customizing Metro (metro.config.js)
Even a single small icon becomes far easier to maintain once you understand how it renders. I hope this saves someone the hour it cost me. Thank you for reading.