Skip to main content

Web Hosting Providers

After creating a static export of your Expo Router web app, you can deploy it to any static hosting provider. This guide covers popular options with step-by-step deployment instructions.

Choosing a Hosting Provider

ProviderBest ForFree TierCustom DomainCDNEdge Functions
EAS HostingExpo projects, unified mobile+webYesYes (paid)YesYes
VercelNext.js-like experience, edge networkYesYesYesYes
NetlifySimple deployment, formsYesYesYesYes
Cloudflare PagesGlobal CDN, speedYesYesYesYes
GitHub PagesGitHub repositories, free hostingYesLimitedNoNo
AWS AmplifyAWS ecosystem integrationFree tierYesYesLimited
RenderFull-stack apps, databasesFree tierYesYesNo
EAS Hosting provides seamless integration with Expo projects and unified deployment for mobile and web.
1

Export Your App

Create a static build:
npx expo export --platform web
This creates a dist directory with your static files.
2

Install EAS CLI

npm install -g eas-cli
eas login
3

Deploy to EAS Hosting

eas deploy
EAS CLI will:
  1. Upload your dist directory
  2. Deploy to global CDN
  3. Provide a preview URL: https://[project].exp.app
4

Set Up Custom Domain (Optional)

Available on paid plans:
  1. Go to EAS Dashboard
  2. Navigate to your project > Hosting
  3. Click Custom domain
  4. Add your domain and configure DNS:
    CNAME  www  [your-project].exp.direct
    
5

Configure Deployment Aliases

Create production alias:
# Create production deployment
eas deploy --alias production

# Or make existing deployment production
eas deploy:alias --prod --id [deployment-id]

EAS Hosting Features

Unified deployment:
  • Deploy web alongside mobile builds
  • Single command for all platforms
  • Integrated with EAS Update for mobile
API routes support:
  • Server functions via Cloudflare Workers
  • Monitor API logs in dashboard
  • Built-in crash reporting
Automatic optimization:
  • Global CDN distribution
  • Automatic compression
  • Edge caching

Vercel

Vercel offers excellent performance and developer experience, similar to their Next.js platform.
1

Install Vercel CLI

npm install -g vercel
2

Configure Vercel

Create vercel.json in your project root:
vercel.json
{
  "buildCommand": "npx expo export --platform web",
  "outputDirectory": "dist",
  "devCommand": "npx expo start --web",
  "framework": null,
  "cleanUrls": true,
  "rewrites": [
    {
      "source": "/:path*",
      "destination": "/"
    }
  ]
}
For static export (not server mode), use the rewrite configuration above to handle client-side routing.
3

Deploy

vercel
Follow prompts to:
  1. Link to existing project or create new
  2. Configure project settings
  3. Deploy
Vercel provides:
  • Preview URL: https://[project]-[hash].vercel.app
  • Production URL: https://[project].vercel.app
4
  1. Push code to GitHub/GitLab/Bitbucket
  2. Go to Vercel Dashboard
  3. Click New Project
  4. Import your repository
  5. Configure:
    • Build Command: npx expo export --platform web
    • Output Directory: dist
  6. Deploy
Automatic deployments on every push to main branch.
5

Custom Domain

  1. Go to Project Settings > Domains
  2. Add your domain
  3. Configure DNS:
    A     @       76.76.21.21
    CNAME www     cname.vercel-dns.com
    
  4. SSL automatically provisioned

Vercel Environment Variables

Add environment variables in dashboard or via CLI:
vercel env add EXPO_PUBLIC_API_URL
# Enter value: https://api.example.com
Or in vercel.json:
{
  "env": {
    "EXPO_PUBLIC_API_URL": "https://api.example.com"
  }
}

Netlify

Netlify provides simple deployment with great CI/CD integration and form handling.
1

Install Netlify CLI

npm install -g netlify-cli
2

Configure Netlify

Create netlify.toml in project root:
netlify.toml
[build]
  command = "npx expo export --platform web"
  publish = "dist"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    X-Content-Type-Options = "nosniff"
3

Deploy

netlify deploy

# For production
netlify deploy --prod
Or connect to Git:
  1. Go to Netlify Dashboard
  2. Click New site from Git
  3. Connect repository
  4. Build settings auto-detected from netlify.toml
  5. Deploy
4

Custom Domain

  1. Go to Site settings > Domain management
  2. Add custom domain
  3. Configure DNS:
    CNAME www [site-name].netlify.app
    
  4. Enable HTTPS (automatic)

Netlify Features

Forms: Add forms with zero configuration:
<form netlify>
  <input type="text" name="name" />
  <input type="email" name="email" />
  <button type="submit">Submit</button>
</form>
Split testing: A/B test different branches:
netlify.toml
[split_testing]
  branch = "feature-branch"
  traffic = 0.2  # 20% traffic to feature branch

Cloudflare Pages

Cloudflare Pages offers global CDN with excellent performance and security.
1

Create Cloudflare Account

Sign up at Cloudflare Pages
2

Deploy from Git

  1. Go to Cloudflare Pages dashboard
  2. Click Create a project
  3. Connect to GitHub/GitLab
  4. Select repository
  5. Configure build:
    • Build command: npx expo export --platform web
    • Build output directory: dist
    • Root directory: (leave empty or set if monorepo)
3

Configure _redirects

Create public/_redirects for client-side routing:
public/_redirects
/*    /index.html    200
4

Custom Domain

  1. Go to Custom domains
  2. Add your domain
  3. Configure DNS (if domain not on Cloudflare):
    CNAME www [project].pages.dev
    
    Or migrate nameservers to Cloudflare for automatic setup

Cloudflare Workers

Add serverless functions:
functions/api/hello.js
export async function onRequest() {
  return new Response(JSON.stringify({ message: 'Hello' }), {
    headers: { 'Content-Type': 'application/json' }
  });
}
Access at: https://[project].pages.dev/api/hello

GitHub Pages

Free hosting for public repositories with GitHub.
1

Install gh-pages

npm install --save-dev gh-pages
2

Configure package.json

Add deployment scripts:
package.json
{
  "scripts": {
    "deploy": "npx expo export --platform web && npx gh-pages -d dist"
  },
  "homepage": "https://[username].github.io/[repo-name]"
}
For custom domain:
{
  "homepage": "https://example.com"
}
3

Create CNAME file

For custom domains, create public/CNAME:
public/CNAME
example.com
4

Deploy

npm run deploy
This:
  1. Exports your app to dist
  2. Pushes dist to gh-pages branch
  3. GitHub Pages serves from that branch
5

Configure Repository

  1. Go to repository Settings > Pages
  2. Source: gh-pages branch
  3. Custom domain: Enter your domain (optional)
  4. Enforce HTTPS: Enabled
6

Configure DNS (Custom Domain)

A     @     185.199.108.153
A     @     185.199.109.153
A     @     185.199.110.153
A     @     185.199.111.153
CNAME www   [username].github.io

GitHub Pages Limitations

  • Single-page app routing requires workaround
  • No server-side logic
  • 100GB bandwidth/month limit
  • 1GB repository size limit

AWS Amplify

AWS Amplify integrates with AWS services and provides full CI/CD.
1

Install Amplify CLI

npm install -g @aws-amplify/cli
amplify configure
2

Deploy from Console

  1. Go to AWS Amplify Console
  2. Click New app > Host web app
  3. Connect repository (GitHub, GitLab, Bitbucket, CodeCommit)
  4. Configure build:
    • Build command: npx expo export --platform web
    • Output directory: dist
  5. Deploy
3

Configure amplify.yml

Create amplify.yml for custom build:
amplify.yml
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - npm install
    build:
      commands:
        - npx expo export --platform web
  artifacts:
    baseDirectory: dist
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*
4

Custom Domain

  1. Go to App settings > Domain management
  2. Add domain
  3. Amplify handles DNS configuration automatically
  4. Or manually configure:
    CNAME www [branch-name].[app-id].amplifyapp.com
    

Amplify Features

Branch deployments:
  • Preview URLs for each branch
  • Automatic builds on pull requests
Environment variables:
  • Managed in Amplify Console
  • Different values per branch/environment

Render

Render provides free static site hosting with automatic SSL.
1

Create Account

Sign up at Render
2

Deploy Static Site

  1. Click New > Static Site
  2. Connect GitHub/GitLab
  3. Select repository
  4. Configure:
    • Build Command: npx expo export --platform web
    • Publish Directory: dist
  5. Create Static Site
3

Custom Domain

  1. Go to site Settings > Custom Domain
  2. Add domain
  3. Configure DNS:
    CNAME www [site-name].onrender.com
    
  4. SSL automatically provisioned

Deployment Best Practices

Automated Deployments

Set up CI/CD for automatic deployments:
.github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Export web
        run: npx expo export --platform web
      
      - name: Deploy to Netlify
        uses: netlify/actions/cli@master
        with:
          args: deploy --dir=dist --prod
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

Environment-Specific Builds

Use different configurations per environment:
package.json
{
  "scripts": {
    "build:staging": "EXPO_PUBLIC_ENV=staging npx expo export --platform web",
    "build:production": "EXPO_PUBLIC_ENV=production npx expo export --platform web",
    "deploy:staging": "npm run build:staging && netlify deploy --dir=dist",
    "deploy:production": "npm run build:production && netlify deploy --prod --dir=dist"
  }
}

Performance Optimization

Enable compression: Most hosts enable automatically, but verify:
netlify.toml
[[headers]]
  for = "/*"
  [headers.values]
    Content-Encoding = "gzip"
Cache static assets:
netlify.toml
[[headers]]
  for = "/_expo/static/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
Optimize images: Use Cloudflare Images, Vercel Image Optimization, or similar:
// Cloudflare Images example
<img 
  src="https://imagedelivery.net/[account]/[image-id]/public"
  alt="Optimized image"
/>

Security Headers

Add security headers to protect your app:
netlify.toml
[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    X-XSS-Protection = "1; mode=block"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Permissions-Policy = "camera=(), microphone=(), geolocation=()"

Monitoring and Analytics

Google Analytics:
app/_layout.tsx
import { useEffect } from 'react';
import { usePathname } from 'expo-router';

export default function RootLayout() {
  const pathname = usePathname();
  
  useEffect(() => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('config', 'GA_MEASUREMENT_ID', {
        page_path: pathname,
      });
    }
  }, [pathname]);
  
  return <Slot />;
}
Vercel Analytics:
npm install @vercel/analytics
app/_layout.tsx
import { Analytics } from '@vercel/analytics/react';

export default function RootLayout() {
  return (
    <>
      <Slot />
      <Analytics />
    </>
  );
}

Troubleshooting

404 Errors on Refresh

Issue: Routes return 404 when directly accessed or refreshed Solution: Configure redirects to index.html:
vercel.json
{
  "rewrites": [
    { "source": "/(.*)", "destination": "/" }
  ]
}

Environment Variables Not Working

Issue: process.env.EXPO_PUBLIC_* is undefined Solutions:
  1. Prefix with EXPO_PUBLIC_:
    EXPO_PUBLIC_API_URL=https://api.example.com
    
  2. Add to hosting provider:
    • Vercel: Project Settings > Environment Variables
    • Netlify: Site settings > Build & deploy > Environment
    • Cloudflare: Settings > Environment variables
  3. Rebuild app (env vars are embedded at build time)

Assets Not Loading

Issue: Images/fonts return 404 Solution: Use absolute paths from root:
// Correct
<Image source={{ uri: '/images/logo.png' }} />

// Wrong (relative path)
<Image source={{ uri: './images/logo.png' }} />

Build Fails on Hosting Provider

Issue: Build works locally but fails in CI Check:
  1. Node version matches:
    package.json
    {
      "engines": {
        "node": ">=18.0.0"
      }
    }
    
  2. Install script:
    {
      "scripts": {
        "postinstall": "npx expo-optimize"
      }
    }
    
  3. Check build logs for specific errors

Next Steps

Build docs developers (and LLMs) love