Overview
Quest Hunter can be deployed as a static web application using Expo’s web support. This guide covers building and deploying the web version to various hosting platforms.
Web Configuration
Quest Hunter is configured for web deployment in app.json:
"web" : {
"output" : "static" ,
"favicon" : "./assets/images/favicon.png" ,
"bundler" : "metro"
}
Key Features
Static Output : Generates static HTML, CSS, and JavaScript files
Metro Bundler : Uses Metro for consistent bundling across platforms
Custom Favicon : Configured favicon for web browsers
React Native Web : Translates React Native components to web
Quest Hunter uses Metro bundler for web builds, ensuring consistency with native builds and supporting the new React Native architecture.
Building for Web
Development Build
Run the development server:
Start development server
bun web
# or
expo start --web
This starts the web development server at http://localhost:8081.
Development with backend
Run both web and backend services: This uses mprocs to run:
Expo development server
Convex backend development server
Production Build
Create a production-optimized static build:
Set environment variables
Ensure your .env file has production values: EXPO_PUBLIC_CONVEX_URL = https://your-production.convex.cloud
EXPO_PUBLIC_CONVEX_SITE_URL = https://your-production.convex.site
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY = pk_live_...
Build static files
bun build
# or
expo export
This generates static files in the dist/ directory.
Verify build output
Check the generated files: You should see:
index.html - Main HTML file
_expo/static/ - Bundled JavaScript and assets
assets/ - Images and other assets
Vercel
Deploy to Vercel for optimal performance:
Configure vercel.json
Create vercel.json in your project root: {
"buildCommand" : "bun build" ,
"outputDirectory" : "dist" ,
"devCommand" : "bun web" ,
"installCommand" : "bun install" ,
"framework" : null ,
"env" : {
"EXPO_PUBLIC_CONVEX_URL" : "@convex-url" ,
"EXPO_PUBLIC_CONVEX_SITE_URL" : "@convex-site-url" ,
"EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY" : "@clerk-publishable-key"
}
}
Add environment variables
vercel env add EXPO_PUBLIC_CONVEX_URL
vercel env add EXPO_PUBLIC_CONVEX_SITE_URL
vercel env add EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY
Deploy
# Deploy to preview
vercel
# Deploy to production
vercel --prod
Netlify
Deploy to Netlify:
Install Netlify CLI
npm install -g netlify-cli
Configure netlify.toml
Create netlify.toml: [ build ]
command = "bun build"
publish = "dist"
[ build . environment ]
NODE_VERSION = "24.14.0"
[[ redirects ]]
from = "/*"
to = "/index.html"
status = 200
Deploy
# Initialize and deploy
netlify init
netlify deploy --prod
GitHub Pages
Deploy to GitHub Pages:
Configure homepage
Add to package.json: {
"homepage" : "https://yourusername.github.io/quest-hunter"
}
Deploy with gh-pages
npm install -g gh-pages
gh-pages -d dist
GitHub Pages doesn’t support environment variables at build time. You’ll need to build locally with production environment variables or use GitHub Actions.
AWS S3 + CloudFront
Deploy to AWS:
Create S3 bucket
aws s3 mb s3://quest-hunter-web
aws s3 website s3://quest-hunter-web --index-document index.html
Upload files
aws s3 sync dist/ s3://quest-hunter-web --delete
Configure CloudFront
Create CloudFront distribution
Set origin to S3 bucket
Configure custom error response for SPA routing
Add custom domain and SSL certificate
Environment Variables
Build-Time Variables
Variables prefixed with EXPO_PUBLIC_ are bundled at build time:
EXPO_PUBLIC_CONVEX_URL = https://your-deployment.convex.cloud
EXPO_PUBLIC_CONVEX_SITE_URL = https://your-deployment.convex.site
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY = pk_live_...
Environment variables are bundled into the JavaScript at build time. Any changes require rebuilding and redeploying the app.
For different environments (staging, production):
Create environment-specific files:
.env.production
.env.staging
Load the appropriate file during build:
# Production
cp .env.production .env && bun build
# Staging
cp .env.staging .env && bun build
Web-Specific Considerations
Google Maps API Key
The Google Maps API key in app.json is for Android only. For web deployment with maps, you need to configure a separate web API key.
If using Google Maps on web:
Create a web-restricted API key in Google Cloud Console
Add HTTP referrer restrictions
Configure in your web app separately from the Android key
Location Permissions
Web browsers handle location permissions differently:
Users see browser permission prompt
HTTPS required for location access
Permission is per-domain
No persistent permission like mobile apps
Location features require HTTPS in production. Local development works over HTTP, but production deployments must use HTTPS.
PWA Configuration (Optional)
To make Quest Hunter installable as a Progressive Web App:
Add manifest.json
Create public/manifest.json: {
"short_name" : "Quest Hunter" ,
"name" : "Quest Hunter - Location-Based Quests" ,
"icons" : [
{
"src" : "favicon.png" ,
"sizes" : "192x192" ,
"type" : "image/png"
},
{
"src" : "favicon.png" ,
"sizes" : "512x512" ,
"type" : "image/png"
}
],
"start_url" : "/" ,
"display" : "standalone" ,
"theme_color" : "#E6F4FE" ,
"background_color" : "#ffffff"
}
Link manifest in HTML
Expo automatically includes the manifest, but verify in dist/index.html: < link rel = "manifest" href = "/manifest.json" >
Add service worker (optional)
For offline support, add a service worker using Workbox or similar tools.
Code Splitting
Expo’s web build automatically splits code by route when using expo-router.
Asset Optimization
Images : Use expo-image which optimizes for web
Fonts : Preload critical fonts
Bundle Size : Analyze with:
expo export --dump-sourcemap
Caching Strategy
Configure caching headers on your hosting platform:
# Static assets (JS, CSS, images)
Cache-Control: public, max-age=31536000, immutable
# HTML files
Cache-Control: public, max-age=0, must-revalidate
Testing Web Build Locally
Test the production build locally:
Serve static files
Or use any static file server: # Python
python -m http.server 8000 --directory dist
# Node.js
npx http-server dist
Test in browser
Open http://localhost:8000 and verify:
App loads correctly
Routing works (try refreshing on different routes)
Environment variables are correct
External services (Convex, Clerk) connect properly
Troubleshooting
Blank Screen on Load
Common causes:
Missing environment variables : Check browser console for errors
CORS issues : Verify API endpoints allow your domain
Build errors : Check build output for warnings
Routing Issues
If routes don’t work after refresh:
Configure your hosting platform for SPA routing
Ensure all requests are redirected to index.html
Check netlify.toml or vercel.json configuration
Asset Loading Failures
If images or assets don’t load:
Verify asset paths are relative
Check build output includes all assets
Ensure hosting platform serves all file types correctly
Large bundle size : Use source map analysis to identify large dependencies
Slow load time : Enable compression (gzip/brotli) on hosting
Render performance : Check React DevTools Profiler
CI/CD Automation
GitHub Actions Example
Create .github/workflows/deploy-web.yml:
name : Deploy Web
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- name : Setup Bun
uses : oven-sh/setup-bun@v1
with :
bun-version : 1.3.9
- name : Install dependencies
run : bun install
- name : Build
env :
EXPO_PUBLIC_CONVEX_URL : ${{ secrets.EXPO_PUBLIC_CONVEX_URL }}
EXPO_PUBLIC_CONVEX_SITE_URL : ${{ secrets.EXPO_PUBLIC_CONVEX_SITE_URL }}
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY : ${{ secrets.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY }}
run : bun build
- name : Deploy to Vercel
uses : amondnet/vercel-action@v25
with :
vercel-token : ${{ secrets.VERCEL_TOKEN }}
vercel-org-id : ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id : ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args : '--prod'
Next Steps
iOS Deployment Deploy to App Store and TestFlight
Android Deployment Deploy to Google Play Store