Quand de nouvelles features entrent dans de vieux systèmes
Un mode de panne récurrent dans les apps React Native vibe codées, c’est qu’une nouvelle feature importe directement un service existant, modifie un state qu’elle ne possède pas et casse un flow sans rapport. L’auth est souvent la première victime parce qu’elle devient vite la dépendance partagée de tout le monde.
Dans des apps assemblées rapidement avec Cursor et Claude Code, chaque feature est générée dans un contexte local. Le code compile. Il passe parfois même le happy path. Mais sans frontières de service, des fichiers indépendants tombent ensemble.
C’est le problème fondamental du vibe coding à l’échelle. Les assistants de code IA sont des architectes aveugles au contexte. Ils génèrent ce que vous demandez, pas ce dont votre codebase a besoin. Sans frontières de service explicites, chaque nouvelle feature devient une régression potentielle en attente.
La règle des interfaces
Voici la règle : chaque service de votre app expose une interface. L’interface vit dans votre codebase. L’implémentation, qu’il s’agisse de Supabase, Firebase, RevenueCat ou d’un mock, reste derrière cette interface. Le reste de votre app ne sait pas laquelle est utilisée.
// src/services/auth/auth.interface.ts
// Your app owns this. Not Supabase. Not Firebase. You.
export interface User {
id: string
email: string
displayName: string | null
avatarUrl: string | null
}
export interface Session {
accessToken: string
refreshToken: string
expiresAt: number
user: User
}
export interface AuthService {
signIn(credentials: EmailCredentials): Promise<Session>
signOut(): Promise<void>
getSession(): Promise<Session | null>
onAuthStateChange(
callback: (session: Session | null) => void
): () => void
}
Voilà la seam. Tout le côté app parle à AuthService. Tout le côté SDK l’implémente. Quand vous voulez remplacer Supabase par Firebase, vous écrivez une nouvelle classe d’implémentation. L’écran de login ne change pas. Les route guards ne changent pas. Les hooks ne changent pas. Claude Code n’a pas besoin de savoir qu’un remplacement a eu lieu.
Le composition root
Un seul fichier construit le graphe de dépendances complet. Partout ailleurs, on consomme.
// src/services/create-services.ts
// ONE file. Cursor never touches this. You own it.
import { config } from '@/constants/config'
import { SupabaseAuthService } from './auth/supabase'
import { RevenueCatService } from './payments/revenuecat'
export type Services = ReturnType<typeof createServices>
export function createServices() {
const storage = createStorageServices(config.storageMode)
return {
storage,
auth: new SupabaseAuthService(storage.secureStorage),
payments: new RevenueCatService(),
analytics: new SegmentAnalyticsService(),
crashReporting: new SentryService(),
network: createNetworkService(),
}
}
Vous voulez remplacer Supabase par Firebase ? Une ligne à changer. Passer de Segment à PostHog ? Une ligne. Voilà à quoi ressemble une architecture React Native scalable quand vous vibe codez avec l’aide de l’IA.
Pourquoi c’est encore plus important dans les apps codées par IA
Quand un humain écrit du code, il garde l’architecture en tête. Il sait qu’il ne faut pas importer supabase directement dans un composant d’écran. Les assistants de code IA, eux, ne gardent pas l’architecture en tête. Ils gardent des fenêtres de contexte. Et les fenêtres de contexte oublient.
// src/context/auth-provider.tsx
// This provider doesn't know what auth SDK you use.
// Claude Code generates against the interface, never the implementation.
export function AuthProvider({ children }: PropsWithChildren) {
const { auth } = useServices()
const [session, setSession] = useState<Session | null>(null)
useEffect(() => {
auth.getSession().then(setSession)
return auth.onAuthStateChange(setSession)
}, [auth])
const signIn = useCallback(
async (creds: AuthCredentials) => {
const s = await auth.signIn(creds)
setSession(s)
},
[auth]
)
return (
<AuthContext.Provider
value={{ session, user: session?.user ?? null, signIn }}>
{children}
</AuthContext.Provider>
)
}
Ce fichier n’a pas changé au fil de trois migrations backend différentes. Il ne sait pas à quoi ressemble supabase.auth.signInWithPassword. Il connaît auth.signIn(). C’est tout l’intérêt.
L’Autotomy Expo Starter Pack livre déjà cette architecture : interfaces de service, composition root, barrel exports, tout est câblé pour que Cursor et Claude Code génèrent du code qui respecte vos frontières dès le premier jour.