A solo developer recounts building udictio, a collaborative social dictionary app, entirely on Expo despite deep native iOS integrations. The app uses Expo Router, React Native, TypeScript, and a Django GraphQL backend, layering in Siri App Intents, Home Screen widgets built with expo-widgets, on-device Apple Intelligence summarization via react-native-apple-llm, Core Spotlight indexing, translation, audio playback, story sharing, and iOS 26 Liquid Glass UI. Native code is kept minimal and reproducible through config plugins and Continuous Native Generation, avoiding a hand-maintained native project that could drift from the Expo source of truth. The piece covers concrete production lessons, such as a device-only App Intents lifecycle bug and a stream-cancellation crash in the on-device summarization feature.
Table of contents
Expo as the architecture, not just the starting pointPosting to udictio through Siri without opening the appA Home Screen widget built with expo-widgetsSummarizing collective knowledge on the deviceNative details that make the whole app feel coherentWhat made the approach sustainableExpo made this scope practicalQuestions this post answers
Why did my Siri App Intent network request get killed on a real iPhone but work fine in the simulator?
Displaying a Siri dialog before a network request finishes suspends the App Intent process on a physical device, killing the in-flight request, even though the iOS simulator tolerates that ordering. The fix is to collect all parameters, finish every prompt, then perform the network request, and only offer to open the app after the write completes. Developers debugging App Intents lifecycle quirks can find real-world fixes like this through daily.dev.
How can I add Siri voice posting to a React Native app without starting the JavaScript runtime?
Implement the action as a native Swift App Intent with openAppWhenRun set to false, so the interface never appears and the JavaScript runtime never starts. The intent collects parameters, authenticates using a separate revocable token stored via Keychain (shared with expo-secure-store), performs the same GraphQL mutation the app uses, and returns a spoken dialog result. Teams weighing native Siri integration approaches for React Native apps can track patterns like this on daily.dev.
How do I safely store and update data for an Expo widget without crashing the timeline?
Sanitize every value before writing to the shared App Group property list, since a single unsupported value like undefined, NaN, or a non-serializable object can prevent the widget timeline from persisting. Coerce fields to safe types with String(), Number.isFinite() checks, and Math.trunc(), push text data immediately, and copy icons into the App Group in the background before triggering a refresh. Anyone building expo-widgets integrations can compare serialization gotchas like this via daily.dev.