EAS Build, Submit Và Update 2026: Deploy React Native Lên App Store Và Google Play Bằng Expo

Bộ ba EAS Build, EAS Submit và EAS Update giúp bạn deploy React Native lên App Store và Google Play năm 2026 mà không cần Mac riêng. Hướng dẫn cấu hình eas.json, runtimeVersion, OTA và CI/CD GitHub Actions với ví dụ thực tế.

EAS Build, Submit, Update 2026 Guide

Cập nhật: 09 tháng 6, 2026

Để deploy app React Native lên App Store và Google Play năm 2026, cách nhanh và ít rủi ro nhất là dùng bộ ba EAS Build (build binary trên cloud), EAS Submit (đẩy file .ipa/.aab lên hai store qua một lệnh CLI) và EAS Update (OTA cho JS/asset bypass review). Workflow chuẩn mình hay dùng: eas init, sau đó cấu hình eas.json với ba profile development, preview, production, rồi eas build --profile production --platform all, tiếp đến eas submit. Khi cần vá bug nóng thì chạy eas update --branch production là xong.

  • EAS Build chạy iOS build trên macOS runner của Expo và Android build trên Linux runner GCP, nên bạn không cần Mac hay phải quản lý keystore thủ công.
  • eas.json chia ba profile chuẩn: development (kèm expo-dev-client), preview (internal distribution để QA), production (ký bằng distribution certificate, dành cho store).
  • EAS Submit upload .aab lên Google Play qua Google Service Account JSON, còn .ipa lên App Store Connect qua App Store Connect API key.
  • EAS Update dùng runtimeVersion để match update với binary. Chính sách appVersion đơn giản nhất, còn fingerprint an toàn nhất.
  • Channels và branches tách deploy staging khỏi production; rollout phần trăm và rollback một lệnh giảm rủi ro release đáng kể.
  • EAS có free tier khá thoáng; chi phí thực tế cho app indie thường chỉ là phí Apple Developer 99 USD/năm và Google Play 25 USD một lần.

EAS là gì và gồm những dịch vụ nào?

Expo Application Services (EAS) là bộ dịch vụ cloud do team Expo phát triển. Nó giải quyết hai nỗi đau lớn nhất khi ship app React Native: vòng build native chậm chạp trên máy local và vòng review App Store mỗi lần fix bug. Honestly, ở dự án gần nhất của mình, một build iOS production trên MacBook Pro M1 mất 18–22 phút và liên tục thất bại vì version Xcode lệch với CI. EAS Build chỉ mất 12–15 phút, và quan trọng hơn là tái lập được giữa các máy khác nhau.

EAS gồm bốn dịch vụ chính bạn sẽ chạm tới hàng tuần:

  • EAS Build: build binary .ipa/.aab trên cloud, tự quản keystore và provisioning profile.
  • EAS Submit: đẩy binary lên App Store Connect và Google Play Console qua một lệnh CLI.
  • EAS Update: push update JS/asset (OTA) bypass app store review cho mọi thay đổi không đụng native code.
  • EAS Workflows: pipeline CI/CD đa bước (build, submit, notify) chạy trên hạ tầng EAS, thay thế nhu cầu config GitHub Actions phức tạp.

Điểm quan trọng cần nhớ năm 2026: EAS không yêu cầu dự án phải là "Expo managed". Với npx expo prebuild hoặc đơn giản là chạy eas build:configure trong bất kỳ dự án React Native CLI nào, bạn có thể tận dụng toàn bộ pipeline mà vẫn giữ code Objective-C/Kotlin tự viết. EAS Build hỗ trợ cả Expo SDK 53+ với React Native 0.79+ lẫn dự án bare workflow truyền thống. Xem chi tiết tại tài liệu chính thức EAS.

Cài đặt EAS CLI và khởi tạo dự án

Bước khởi đầu chỉ mất khoảng năm phút. Trước khi bắt đầu, đảm bảo dự án của bạn đã chạy được npx expo run:ios hoặc đã có thư mục ios/android/ hợp lệ; EAS không sửa lỗi build native local cho bạn đâu.

# 1. Cài EAS CLI toàn cục
npm install -g eas-cli

# 2. Đăng nhập tài khoản Expo
eas login

# 3. Vào thư mục dự án và liên kết với EAS project
cd my-react-native-app
eas init

# 4. Sinh file eas.json mặc định
eas build:configure

Lệnh eas init tạo projectId trong app.json dưới extra.eas.projectId. Đây là khóa để EAS biết binary thuộc về dự án nào trên dashboard. Tuyệt đối không xóa hoặc commit hai projectId khác nhau lên cùng repo vì Submit và Update sẽ dò sai project.

Sau khi chạy eas build:configure, bạn sẽ thấy file eas.json tối thiểu xuất hiện ở root. Đây là toàn bộ "infrastructure as code" của pipeline deploy. Mọi quyết định về môi trường, version Node, máy chạy build, biến môi trường, định dạng output đều nằm trong file này. Bước tiếp theo là hiểu rõ ba profile chuẩn để không lặp lại lỗi kinh điển của người mới: build production với debug certificate hoặc upload nhầm staging lên store.

Hiểu sâu cấu hình eas.json: ba profile chuẩn

File eas.json mặc định trông như sau và là điểm xuất phát đủ tốt cho hầu hết dự án:

{
  "cli": { "version": ">= 12.0.0", "appVersionSource": "remote" },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": { "simulator": true },
      "env": { "APP_ENV": "development" }
    },
    "preview": {
      "distribution": "internal",
      "channel": "preview",
      "env": { "APP_ENV": "staging" }
    },
    "production": {
      "autoIncrement": true,
      "channel": "production",
      "env": { "APP_ENV": "production" }
    }
  },
  "submit": {
    "production": {
      "ios": {
        "ascAppId": "1234567890",
        "appleTeamId": "ABCDE12345"
      },
      "android": {
        "serviceAccountKeyPath": "./google-play-service-account.json",
        "track": "internal"
      }
    }
  }
}

Profile development

Build bao gồm expo-dev-client, không bao giờ được submit lên store. "ios": { "simulator": true } sinh .app để chạy trên iOS Simulator (không ký certificate); bỏ tham số đó để build cho máy thật. Mục tiêu là dev nội bộ có thể quét QR cài app rồi reload JS bundle qua Metro.

Profile preview

Distribution "internal" cho phép share link build cho QA và stakeholder cài trực tiếp mà không cần TestFlight. Channel "preview" tách EAS Update khỏi production, nên bug fix push qua channel này không ảnh hưởng người dùng cuối. Đây là môi trường lý tưởng để kiểm thử release candidate trước khi bắn lên store.

Profile production

Build ký bằng distribution certificate, đầy đủ entitlement và optimize bundle. autoIncrement: true giúp tự bump buildNumber trên iOS và versionCode trên Android. Bỏ tay là chuyện một lần upload bị reject vì duplicate version (mình đã ăn quả này hai lần rồi).

Chạy EAS Build cho iOS và Android

Sau khi config xong, build production cho cả hai nền tảng chỉ tốn một dòng lệnh:

# Build cho cả hai nền tảng song song
eas build --platform all --profile production

# Hoặc tách rời
eas build --platform ios --profile production
eas build --platform android --profile production

# Build local trên máy bạn (debug hoặc compliance)
eas build --platform ios --profile production --local

Lần đầu chạy, CLI sẽ hỏi loạt câu hỏi về credentials. Với iOS, bạn nhập Apple ID và CLI sẽ tự đăng nhập App Store Connect, sinh hoặc reuse distribution certificate, provisioning profile và push notification key. Với Android, CLI hỏi muốn tự sinh keystore hay upload keystore có sẵn. Toàn bộ artifact này được lưu mã hóa trên server Expo và bạn có thể tải về qua eas credentials bất cứ lúc nào.

iOS build chạy trên macOS runner (Xcode mới nhất tương thích Apple); Android build chạy trên Linux runner Google Cloud Platform. Build production iOS thường mất 12–18 phút, Android 6–10 phút. Free tier có một build chạy đồng thời, và queue có thể chờ vài phút giờ cao điểm; gói paid mở thêm concurrency và priority queue. Tham khảo EAS Build docs để biết runner image hiện tại.

Sau khi xong, bạn nhận URL artifact để tải .ipa hoặc .aab về máy hoặc share QR cài trực tiếp lên device test. Trên dashboard expo.dev, mỗi build hiển thị log đầy đủ (CocoaPods, xcodebuild, gradle), commit SHA, branch và profile dùng. Cực kỳ tiện khi cần audit "build này thực ra chứa code nào".

Quản lý credentials: keystore, provisioning profile và App Store Connect API key

Credentials là phần khiến developer mới nhất hay nản nhất khi deploy mobile. EAS giải quyết bằng cách quản lý hộ, bạn chỉ cần hiểu ba thứ.

Android keystore là file .jks dùng để ký APK/AAB. Mất keystore là không update được app trên Play Console, đây là sự thật phũ phàng nhiều team đã học bằng đau thương. EAS sinh keystore tự động và lưu mã hóa, bạn có thể download backup bằng eas credentials. Tuyệt đối lưu backup keystore ở nơi an toàn ngoài EAS: vault công ty, 1Password, hoặc S3 mã hóa.

iOS provisioning profile + distribution certificate đi đôi với nhau. EAS có thể tự sinh hoặc đồng bộ qua API App Store Connect; lệnh eas credentials mở menu tương tác để view, rotate, hoặc nhập credential thủ công.

Để EAS Submit hoạt động hoàn toàn tự động, bạn cần hai thứ sau:

  • App Store Connect API key: tạo tại App Store Connect → Users and Access → Integrations → Team Keys. Lưu file .p8 và copy Key ID + Issuer ID. Đây là cách duy nhất tránh hỏi 2FA mỗi lần submit.
  • Google Play Service Account JSON: tạo trong Google Cloud Console, cấp role "Release manager" trên Play Console rồi tải JSON về và lưu vào EAS Secrets hoặc ./google-play-service-account.json (đã add vào .gitignore).

EAS Submit: đẩy app lên App Store và Google Play

Sau khi build xong, submit chỉ cần một lệnh. EAS sẽ chọn binary mới nhất, upload qua API tương ứng và in URL theo dõi tiến độ:

# Submit cả hai nền tảng
eas submit --platform all --profile production --latest

# Chỉ định build cụ thể qua URL
eas submit --platform ios --url https://expo.dev/artifacts/eas/abc.ipa

Phía iOS, EAS upload lên TestFlight qua App Store Connect API key đã cấu hình. Bạn có thể vào App Store Connect, qua tab TestFlight để xem build "Processing" rồi sau đó submit cho TestFlight external testers hoặc App Store review. Nếu muốn EAS submit thẳng lên App Store review thay vì TestFlight, kết hợp với submitToTestFlight: false và thêm release notes.

Phía Android, track trong eas.json quyết định đích đến: internal (Internal testing), alpha (Closed testing), beta (Open testing), hoặc production. Workflow đề xuất 2026 là submit internal trước, chạy automated test, promote sang production qua Play Console hoặc lệnh eas submit riêng. Cách này tận dụng staged rollout của Google Play (1%, 5%, 50%, 100%) để giảm blast radius nếu có bug nghiêm trọng. Xem thêm tài liệu Play Console về release tracks.

Sau khi binary lên store, mọi update JS/asset tiếp theo nên đi qua EAS Update thay vì build mới. Bạn có thể đọc thêm chi tiết về workflow deploy end-to-end trong bài Expo Router 2026 với deep linking để hiểu cách kết hợp deep link và universal link với chu kỳ release.

EAS Update: OTA cho JS và asset bypass review

EAS Update là "vũ khí bí mật" của React Native năm 2026. Khi bug được phát hiện sau release, bạn không cần làm lại build, không cần đợi 24–48 giờ review của Apple. Chỉ cần một lệnh, JS bundle mới có mặt trên CDN toàn cầu trong vài phút.

# Cài expo-updates trong dự án
npx expo install expo-updates

# Cấu hình app.json
{
  "expo": {
    "runtimeVersion": { "policy": "appVersion" },
    "updates": {
      "url": "https://u.expo.dev/your-project-id",
      "fallbackToCacheTimeout": 0,
      "checkAutomatically": "ON_LOAD"
    }
  }
}

# Push update vào branch production
eas update --branch production --message "Fix crash trên màn hình profile"

# Rollback nhanh: re-publish update cũ
eas update --branch production --republish --group <previous-update-group-id>

Cơ chế bên trong: EAS CLI bundle JS+asset bằng Metro giống như build production, upload lên CDN của Expo (thực chất là CloudFront), gắn với một branch. Mỗi binary trên device được liên kết một channel (ví dụ production) và channel này map sang branch tương ứng trên dashboard. Khi user mở app, expo-updates hỏi CDN có update mới khớp runtimeVersion và channel hay không, tải nền và áp dụng ở lần restart kế tiếp.

Sức mạnh thực sự nằm ở rollout phần trăm: eas update:rollout --branch production --percentage 5 phát hành cho 5% user, nếu Sentry không hú lên thì tăng dần lên 25%, 50%, 100%. Đây là cách an toàn nhất triển khai bug fix nóng mà vẫn giữ được khả năng rollback tức thì. Để hiểu sâu cách kết hợp EAS Update với hệ thống monitor error, xem bài debug React Native với DevTools, Reactotron và Sentry.

runtimeVersion và policy: appVersion vs fingerprint

runtimeVersion là chuỗi định danh "phiên bản native runtime" của binary. EAS Update sẽ chỉ phân phối bundle khi binary và update có cùng runtimeVersion + platform + channel. Hiểu sai khái niệm này dẫn đến hai sự cố kinh điển: update không nhận được trên thiết bị (mismatch âm thầm), hoặc tệ hơn, update được nhận trên binary cũ thiếu native module, gây crash trắng màn hình.

Năm 2026 có ba chính sách phổ biến để khai báo trong app.json:

  • { "policy": "appVersion" }: runtime version trùng với version trong app.json (ví dụ "1.2.0"). Đơn giản nhưng đòi hỏi kỷ luật, vì bạn phải bump version mỗi khi đụng native code.
  • { "policy": "fingerprint" }: EAS tự hash toàn bộ native dependency, config plugin và lock files. Bất kỳ thay đổi nào ảnh hưởng native sẽ thay đổi fingerprint, đảm bảo không bao giờ mismatch âm thầm. Trade-off: bạn phải build lại thường xuyên hơn.
  • Chuỗi cố định thủ công như "1.0.0": kiểm soát hoàn toàn, dành cho team có quy trình release nghiêm ngặt.

Trong thực tế của mình, khuyến nghị cho team trên 3 người là dùng fingerprint. Chi phí build dư thừa thấp hơn nhiều so với chi phí debug một crash production do mismatch (mình đã hit đúng cái bug này khi ship một bản update v1.4.2 quên bump appVersion, mất nguyên buổi sáng để truy nguyên). Xem chi tiết tại runtime versions docs.

Channels và branches

Channel là thuộc tính bake vào binary tại build time (qua eas.json); branch là khái niệm logic trên dashboard EAS Update. Một channel có thể trỏ sang nhiều branch theo thời gian. Đây chính là cơ chế "promote update từ staging sang production" mà không cần build lại: chỉ cần đổi branch mà channel production trỏ tới qua eas channel:edit.

Tích hợp CI/CD với GitHub Actions và EXPO_TOKEN

Để tự động hóa hoàn toàn pipeline, tạo EXPO_TOKEN tại expo.dev/settings/access-tokens và lưu vào GitHub Secrets. Workflow ví dụ dưới đây build production và submit khi push tag v*:

name: Build and Submit

on:
  push:
    tags: ['v*']

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: npm ci
      - run: eas build --platform all --profile production --non-interactive --no-wait
      - run: eas submit --platform all --profile production --latest --non-interactive

Lưu ý --non-interactive bắt buộc trong CI vì không có TTY để hỏi prompt. --no-wait cho phép GitHub Actions hoàn thành ngay sau khi enqueue build, tránh tốn minute. EAS sẽ gửi webhook hoặc Slack notification khi build hoàn tất.

Với app đang scale lớn, cân nhắc tách workflow OTA riêng: mỗi push lên main chạy eas update --branch production --auto để release JS-only thay đổi tức thì, trong khi tag release mới mới trigger build binary đầy đủ. Cách này rút thời gian từ commit tới production từ vài ngày xuống vài phút, và phù hợp với app đang trong giai đoạn iterate nhanh. Nó kết hợp tốt với chiến lược tối ưu khởi động đã trình bày trong bài tối ưu hiệu suất React Native 2026.

Các lỗi phổ biến và cách xử lý

Dưới đây là những vấn đề mình thấy lặp lại nhiều nhất trong các project Vietnamese-led mà mình tư vấn năm qua, kèm cách xử lý dứt điểm.

"No bundle URL present" khi mở dev build

Nguyên nhân: Metro chưa chạy hoặc thiết bị không thấy IP của máy dev. Fix: chạy npx expo start --dev-client, đảm bảo máy và phone cùng Wi-Fi, hoặc dùng tunnel --tunnel khi mạng công ty chặn LAN discovery.

iOS build thất bại với "No matching profiles found"

Bundle identifier trong app.json không khớp với App ID đã tạo trên Apple Developer Portal. Đôi khi do typo, đôi khi do team ID khác nhau. Chạy eas credentials → iOS → "Add new" để EAS tự fetch và đồng bộ.

Android upload bị reject "Version code X has already been used"

Bạn đã upload binary cùng versionCode lần trước. Bật "autoIncrement": true trong profile production hoặc dùng "appVersionSource": "remote" để EAS tự quản version trên server (loại bỏ hẳn vấn đề này).

EAS Update không tới được device

Kiểm tra ba thứ: channel trong build hiện tại (eas channel:view), branch mà channel đang trỏ vào, và runtimeVersion của binary so với update. Lệnh eas update:view --group <id> trả về metadata đầy đủ. Khoảng 90% lỗi mình gặp là runtimeVersion mismatch âm thầm. Chuyển sang policy fingerprint để loại trừ.

"Provisioning profile is expired"

Apple gia hạn certificate 12 tháng. Chạy eas credentials → iOS → "Remove provisioning profile" rồi "Add new" để EAS tự sinh lại. Sau đó rebuild, không cần thay đổi code.

Câu hỏi thường gặp

EAS Build có miễn phí không?

Có, mọi tài khoản Expo đều có quota build miễn phí (khoảng 30 build/tháng cho cá nhân tính đến giữa 2026). Gói paid như Production hay Enterprise mở thêm concurrency, priority queue và resource class lớn hơn để tăng tốc build. Lưu ý phí Apple Developer 99 USD/năm và Google Play 25 USD một lần là riêng, không thuộc Expo.

EAS có dùng được với React Native CLI thuần không, không phải Expo?

Có. EAS Build hoạt động với mọi dự án React Native, kể cả bare workflow tạo bằng npx react-native init. Chỉ cần chạy eas build:configure trong root project. Bạn vẫn tự sửa ios/android/ nếu cần, EAS chỉ build và ký hộ.

EAS Update khác CodePush của Microsoft ra sao?

App Center CodePush đã ngừng cập nhật từ 2025 và Microsoft khuyên migrate sang giải pháp khác. EAS Update là người kế nhiệm thực tế: hỗ trợ runtime version policy chính thức, end-to-end code signing, rollout phần trăm và rollback một lệnh, đều mạnh hơn CodePush. Ngoài ra EAS Update bundle bằng Metro chuẩn nên không có "JSI bridge" patch như CodePush hay gặp.

Có cần Mac để build iOS với EAS không?

Không. EAS Build chạy iOS build trên macOS runner của Expo trên cloud, nên bạn có thể deploy iOS app từ Windows hay Linux. Tuy nhiên, dev local (chạy simulator, debug Xcode) vẫn cần Mac thật hoặc dịch vụ CI Mac như MacStadium.

Mất bao lâu để app duyệt qua App Store và Play Store sau khi submit?

Tính tới 2026, App Store review trung bình 24–48 giờ, đôi khi 6–12 giờ cho update đơn giản. Google Play Internal testing đẩy lên gần như tức thì; bản production thường 1–3 ngày. EAS Update không qua review nên thay đổi JS có mặt trên user trong vòng vài phút.

Về Tác Giả Editorial Team

Our team of expert writers and editors.