Quick start
From nothing to switching previews in your app, against a local S3 server.
This page gets one preview running in your own app in a few minutes. It uses a local MinIO server, so you need no cloud account. When it works, swap the local server for a real bucket from Storage. Nothing else changes.
You need Docker (Docker Desktop, OrbStack, or the Docker engine on Linux; Podman's Docker socket works too), git, and an Expo app on SDK 53 or later with the package installed.
Start a local S3 server
The repository has a ready-made MinIO setup with one public bucket, one private bucket and a read-only key. It is not part of the npm package, so take it from the repository:
git clone --depth 1 https://github.com/axonpack/axonpack.git
cd axonpack/packages/@axonpack/preview
./local-s3/run.sh startIt listens on http://localhost:9000. The values are local test values only. See
MinIO for what it creates and how to stop it.
Tell the app where previews are
In your app's root, create .env. Expo builds every EXPO_PUBLIC_* value into the app, so this
file holds only the address, never a key that can write:
EXPO_PUBLIC_PREVIEW_S3_URL=http://localhost:9000/previews-publicTell the CLI where to publish
Create .env.local next to it. Expo's project template gitignores .env*.local, so the write key
stays out of git:
PREVIEW_S3_URL=http://localhost:9000/previews-public
PREVIEW_S3_ACCESS_KEY_ID=preview-admin
PREVIEW_S3_SECRET_ACCESS_KEY=preview-admin-secretThese names have no EXPO_PUBLIC_ prefix, so Expo never builds them into the app.
Add the plugin, the provider and the list
{
"expo": {
"plugins": ["@axonpack/preview"]
}
}import { PreviewList, PreviewProvider } from '@axonpack/preview';
export default function App() {
return (
<PreviewProvider
config={{ enabled: true, url: process.env.EXPO_PUBLIC_PREVIEW_S3_URL ?? '' }}
>
<PreviewList style={{ flex: 1 }} />
</PreviewProvider>
);
}In a real app the list goes on a screen QA can reach, and the provider wraps the whole app. See App setup.
Build and run
npx expo run:ios # or: npx expo run:androidMake sure the device can reach the server:
- iOS simulator:
localhostworks as it is. - Android emulator, or a phone on USB: run
adb reverse tcp:9000 tcp:9000once each time the device starts. - A phone on Wi-Fi: use the computer's LAN address in both URLs instead of
localhost.
The local server is plain HTTP. A debug build reaches it as it is. An Android release build
refuses plain HTTP unless the app allows it, so for a release build against the local server, turn
cleartext on in your app's own config (expo-build-properties, android.usesCleartextTraffic).
Real buckets use HTTPS and need none of this.
Publish a preview
Make a change you can see, for example a different title, then from the app's root:
npx preview publish --id task-1-login-fixIt builds for Android and iOS and uploads both. In a terminal it asks for the title QA sees,
prefilled with "Task 1 login fix"; Enter keeps it. Without --id the id is the current git branch.
Now undo the change in your code. In a debug build the base is whatever Metro serves, so this way the base and the preview look different.
Switch to it
Open the list in the app and tap Refresh. The preview is there. Tap Download, then Switch. The app restarts into the preview and shows your change. Tap Back to base to return.
Publish the same id again with another change and the row says Update available.
Next
- Pick a real bucket: AWS S3, Cloudflare R2 or your own MinIO.
- Publish every pull request: CI.
- Nothing in the list? Troubleshooting.
Next: Storage