#react-native #expo #web #metro #debug

Tela branca no Expo Web. o bundle inteiro morria antes de executar

Miuna Hamasaki

feliz valentine days passei ele brigando com uma tela branca.

o Lógica Viva (Expo SDK 54 + React Native Web) simplesmente abria uma pagina em branco no navegador. tipo, nada. zero. void. o HTML carregava certinho (titulo "Lógica Viva", div #root presente), o Metro servia o bundle JS... mas nenhum conteudo React aparecia. tela branca total. (×_×)

nao tinha nenhum erro visivel na pagina. so o branco absoluto te encarando.

A Investigacao (com Playwright pq sim)

como o VS Code nao tem acesso ao console do navegador, usei o Playwright (headless Chromium) pra capturar programaticamente o que tava acontecendo: conteudo do DOM, erros de console JS, tudo.

e la veio o erro:

Cannot use 'import.meta' outside a module

hmm. interessante. e terrivel.

A Causa Raiz: zustand v5 + Metro = desastre silencioso

o zustand v5.0.11 distribui duas versoes do codigo:

Build Arquivo Variavel de ambiente
CJS (CommonJS) middleware.js process.env.NODE_ENV
ESM (ES Modules) esm/middleware.mjs import.meta.env.MODE

o package.json do zustand tem exports condicionais. quando o Metro resolve modulos pra plataforma web, ele aplica a condicao import, selecionando os arquivos .mjs (ESM).

so que o Metro gera bundles no formato classic script (<script src="...">), e nao <script type="module">. nesse contexto, import.meta eh um SyntaxError que mata o parsing do bundle inteiro antes de qualquer codigo executar. QUALQUER codigo. o React nem chega a rodar. rip.

O MAIS DIABÓLICO

o Metro compilava sem erros. a pagina HTML retorna status 200. so que o JS tem um SyntaxError no parse que acontece silenciosamente. sem feedback visual nenhum. vc so ve o branco. (ꐦ°᷄д°᷅)

quando o bundle ta quebrado assim, o Metro na vdd retorna JSON com status 500 em vez de JavaScript. ai o navegador recusa executar pq o MIME type eh application/json. genial.

A Correcao Principal: metro.config.js

criei um metro.config.js pra forcar a condicao de export react-native (que aponta pros builds CJS) e remover a condicao import (que selecionava os ESM com import.meta):

const { getDefaultConfig } = require('@expo/metro-config');
const config = getDefaultConfig(__dirname);

// Prioriza 'react-native' (CJS) sobre 'import' (ESM)
const conditions = config.resolver.unstable_conditionNames;
if (!conditions.includes('react-native')) {
  conditions.unshift('react-native');
}
// Remove 'import' pra que os .mjs nao sejam selecionados
const importIndex = conditions.indexOf('import');
if (importIndex !== -1) {
  conditions.splice(importIndex, 1);
}

module.exports = config;

resultado: o bundle passa a usar process.env.NODE_ENV (CJS) em vez de import.meta.env (ESM). sem mais SyntaxError

Plot Twist: segundo erro

achei q ia comemorar... mas nao. depois de matar o import.meta, apareceu:

supabaseUrl is required.

o src/services/supabase.ts chamava createClient('', '') quando as env vars EXPO_PUBLIC_SUPABASE_URL e EXPO_PUBLIC_SUPABASE_ANON_KEY nao tavam definidas. o @supabase/supabase-js lanca excecao com URL vazia, matando a avaliacao do modulo e impedindo a montagem do React. mais uma vez: tela branca sem explicacao. ¯\_(ツ)_/¯

fix: placeholder URL pra modo offline.

export const supabase = createClient<Database>(
  supabaseUrl || 'https://placeholder.supabase.co',
  supabaseAnonKey || 'placeholder-key',
  { ... }
);

nao funcional pra operacoes reais, mas o app inicializa e opera em modo offline.

Todas as Correcoes

Arquivo O que fez
metro.config.js (novo) Forca Metro a usar builds CJS em vez de ESM
app.json "bundler": "metro" na config web
src/services/supabase.ts Placeholder URL/key quando env vars ausentes
src/App.tsx ErrorBoundary + linking config + documentTitle
src/services/notifications.ts Lazy-load condicional de modulos nativos
src/hooks/useSync.ts Lazy-load de NetInfo + fallback navigator.onLine
package.json @expo/metro-config como dependencia

Verificacao

rodei teste automatizado com Playwright headless e a tela principal renderizou completa:

Children: 1
Text: " 0 XP / Nv 1 / 0 dias / Daily Review
       Conjuntos e Contagem ..."

HomeScreen com TopBar (XP, nivel, streak), mapa de progresso e barra de navegacao inferior. tudo funcionando.

Licao aprendida

MORAL DA HISTÓRIA

esse cenario eh uma armadilha brutal pq tudo parece ok: Metro compila sem erros, HTML carrega com status 200, a div #root existe... mas o JavaScript morre no parse antes de executar uma unica linha. e a pagina nao te da nenhum sinal.

a unica forma de detectar foi inspecionar o console do navegador (via headless browser). se eu tivesse ficado olhando a tela branca sem abrir o console, ia estar la ate agora.

moral: se a tela ta branca e nao tem erro visivel, roda um headless browser e captura os erros de console. e desconfie dos exports condicionais dos pacotes npm, pq eles podem escolher o build errado pro seu bundler. (⌐■_■)