Skip to main content

Next.js to React Native: A 13-Step Guide to a Real Native App

Next.js to React Native: Follow this 13-step guide to rebuild your web app as a real native iOS and Android app—with routes, testing, and release tips.

Uku Joost Annus··30 min read
Next.js to React Native: A 13-Step Guide to a Real Native App

Your Next.js app works in a browser, but its DOM elements, CSS, routing, storage, and server assumptions do not run unchanged on a phone. A native port preserves the product while rebuilding those browser-bound parts for iOS and Android.

The work usually breaks at HTML components, browser APIs, URL routing, server-only code, and device permissions. Shared TypeScript logic may transfer, but the mobile interface needs native components rather than a WebView wrapper.

This guide gives you a practical 13-step path from codebase audit to app-store release. You will leave with a route decision, a porting plan, and clear checks for native behavior on both platforms.

Quick route decision

  • PWA: Keep browser distribution when an installable website is enough.
  • Capacitor: Keep the web interface when a thin store-distributed shell meets the product's needs.
  • Manual React Native port: Choose full engineering control when you need custom native modules or architecture.
  • Bilt-assisted rebuild: Use the Next.js repository as context for a separate React Native app, then review parity and native behavior.

Next.js and React Native Conversion

Moving from Next.js to React Native lets you reuse platform-neutral logic, state, types, and validation. Components built around HTML, CSS, or Next.js runtime features still need native replacements.

The reusable boundary falls between framework-independent code and code tied to the browser or Next.js runtime.

Plan the boundary by deciding what to share, adapt, and keep on the server:

  • Share: Pure TypeScript, domain types, validation, utilities, non-DOM hooks, and API clients.
packages/shared/       # domain types, validation, API contracts
apps/web/              # Next.js routes and web-only components
apps/mobile/           # React Native screens and native-only code
packages/platform/     # storage.web.ts and storage.native.ts
  • Adapt: Browser storage, permissions, navigation, styling, and components tied to HTML or CSS.
  • Keep server-side: Database access, secrets, SSR, API routes, Server Actions, and edge logic.

React Native uses primitives such as View, Text, and Pressable instead of web elements like div, p, and a. Browser APIs such as window, document, and localStorage need native-safe replacements.

Repository access gives the migration useful source context, not a one-click conversion. Keep browser and native behavior behind files such as storage.web.ts and storage.native.ts, then adapt each screen deliberately.

1. Choose the Migration Route

Choose the route based on distribution, native UI, device access, and how much engineering control you want.

Each route makes a different tradeoff:

  • PWA: Browser distribution and browser-supported APIs, with no native store release in this path.
  • Capacitor or another wrapper: Store distribution while keeping the web UI in a WebView and adding device access through plugins.
  • Manual React Native port: Native screens, direct APIs, custom modules, and full architectural control.
  • Bilt-assisted React Native rebuild: A separate native project informed by the existing web repository, with less manual setup.

Choose React Native when store distribution, native interaction, and deeper device access matter. Bilt reads the Next.js repository as context for a separate React Native project; it does not mutate the web source or convert every component one-to-one.

2. Audit and Sequence the Port

Audit the port by inventorying browser-only dependencies and critical user journeys, then migrate shared logic before presentation screens or device features.

Treat the project as a runtime and interaction migration rather than a packaging task. A WebView wrapper keeps browser assumptions and can raise minimum-functionality concerns under Apple App Store Review Guideline 4.2.

Use this migration order:

  1. Map routes to user journeys. List every Next.js route, the user goal it supports, its data dependencies, analytics events, accessibility needs, lifecycle behavior, and mobile destination.
  2. Flag web-only dependencies. Search for window, document, localStorage, CSS modules, and browser-specific libraries; record an owner and mark each item to replace, adapt, or remove.
  3. Separate the shared core. Isolate API clients, state stores, validation rules, and utility functions behind platform adapters so browser APIs do not leak into React Native.
  4. Port authentication and session state. Confirm sign-in, sign-out, token refresh, and protected access before scheduling the remaining screens.
  5. Sequence screens and native services. Rebuild journeys in dependency order, then add notifications, camera access, or offline storage after the related flow is stable.

Keep a gap tracker beside the route inventory. Mark each journey not started, in progress, or matched, and record the failing step instead of marking an entire feature complete.

Use these journeys to define the initial migration scope. Final approval comes later, after each applicable journey passes on both platforms.

  • Sign-in: registration, login, logout, password reset, session expiry, and protected screens
  • Onboarding: first launch, permissions, skipped steps, and returning-user behavior
  • Core flows: create, read, and update the app's primary records, including empty states
  • Payments, if present: purchase, cancellation, failed payment, and restored access
  • Notifications: permission choice, receipt, tap destination, and disabled-permission behavior
  • Account settings: profile edits, preferences, security controls, and account deletion
  • Error states: offline use, API failure, invalid input, expired sessions, and retry behavior
  • Lifecycle and access: background return, interrupted forms, text scaling, screen-reader labels, focus order, and reduced-motion behavior

Parity means the same user goal succeeds on mobile, even when React Native uses a different navigation path or platform control.

3. Connect the Source Repository

Use Bilt Web App Sync to give Bilt read-only context from an existing Next.js repository while it works on a separate React Native project. It reads routes, flows, and reusable logic without producing a one-to-one conversion.

  1. Connect only the source repository. Connect GitHub in Bilt, grant the Bilt GitHub App access only to the selected Next.js repository, then open Settings → Convert Website and click Connect repository.
  2. Keep the web project as the reference. Each session uses a sandbox clone of the selected repository and never pushes changes back to it.
  3. Build against a separate mobile target. Ask Bilt to compare the web source with the React Native project and use relevant flows as context. You still approve screen parity, native behavior, and product decisions.
  4. Use a separate target repository. Sync or export the React Native project to a team-owned GitHub repository. Protect main, require pull-request review, and run CI checks before merges.

Least-privilege access, branch protection, review rules, and CI requirements are team policies rather than Bilt defaults.

4. Port Screens and UI

Port each Next.js screen by recreating its visual structure with React Native primitives, reconnecting reusable logic, and checking the result against the matching web route.

Web JSX depends on the browser DOM, so tags such as div and span cannot move directly into a native runtime. Start with the closest React Native building block:

  • div or sectionView
// Next.js
<button onClick={save}>Save</button>

// React Native
<Pressable onPress={save} accessibilityRole="button">
  <Text>Save</Text>
</Pressable>
  • Paragraphs, spans, and headings → Text
  • imgImage
  • buttonPressable
  • input or textareaTextInput
  • Long collections → FlatList; bounded content → ScrollView

Work route by route so every native screen has a clear web baseline. Recreate semantics too: add accessibility roles and labels, preserve focus order, and use native controls where platform conventions improve the flow.

  1. Choose one screen and define its states. Open the Next.js route and note the content hierarchy, loading state, empty state, error state, and signed-in or signed-out view.
  2. Recreate the layout with native primitives. Match the order, grouping, imagery, controls, and scroll behavior before refining platform-specific presentation.
  3. Reconnect flows and reusable logic. Preserve platform-neutral validation, state handling, and business rules where practical. Rebuild authentication screens as native UI, then test sign-in, sign-out, and failure paths.
  4. Run a parity check. Compare the web and mobile implementations for the same route, list missing behavior or visual gaps, fix them, and repeat before moving to the next screen.

A useful parity prompt is: “Compare the web and mobile versions of the account screen. List missing states, controls, validation behavior, and visible content, then update the mobile screen to match.”

Keep the web route and native preview side by side. Compare the same state in both, fix one visible or behavioral gap, and rerun the flow before moving to the next screen.

Use FlatList for long collections, size and cache images, and avoid unnecessary re-renders. Judge startup and scrolling in a release build, not only in development.

5. Migrate Styles and Themes

Migrate Next.js styles by replacing CSS classes with React Native StyleSheet objects or NativeWind utilities, adapting layout defaults to Yoga flexbox, and re-binding theme tokens.

Translate CSS rules into native style values, then centralize shared tokens before tuning individual screens. Check the first render early: Yoga can interpret familiar flex rules differently.

  • display: flex → Flex layout is already the default.
  • Web's default row direction → React Native's default flexDirection: 'column'.
  • 16px or 1rem → Numeric density-independent units.
  • margin: 10px 20pxmarginVertical: 10 and marginHorizontal: 20.
  • Media queries → useWindowDimensions() or NativeWind breakpoints.
  • CSS variables → JavaScript or typed theme tokens.

Start by checking container direction and spacing. A web header that relied on the browser's row default needs flexDirection: 'row', while a card stack often works with the native column default.

Choose one styling path and use it consistently:

  • NativeWind: keeps Tailwind-style utility classes and translates them for iOS, Android, and web.
  • StyleSheet: groups native style objects close to the component, with no CSS file or cascade to manage.
  • Tamagui or Unistyles: centralizes typed tokens and cross-platform style rules when the app shares a broader design system.

Utility names can survive a NativeWind migration, but inherited rules, pseudo-classes, grid behavior, and browser selectors need native equivalents or component state. Test safe areas, platform fonts, text scaling, and keyboard overlap on both platforms.

Use live window dimensions for responsive decisions because a phone's available width can change during rotation or split-screen use.

Responsive-design checklist

  • Read width and height with useWindowDimensions() inside the component.
  • Convert each meaningful web breakpoint into a small or wide layout decision.
  • Use NativeWind prefixes such as sm: and md: when utility classes already define the design.
  • Test text scaling and both orientations on real target devices.

Move CSS variables into a theme object or typed token map, then expose the active theme through React Context or the styling library's provider.

Theme checklist

  • Map colors, spacing, typography, radii, and shadows to named tokens.
  • Replace var(--token) calls with direct token references.
  • Use useColorScheme() to follow the device's light or dark setting.
  • Define a fallback theme for an unavailable or unspecified system preference.
  • Check contrast and platform-specific shadow output in both themes.

6. Rebuild Routing and Navigation

Replace Next.js URL routing with native stacks, tabs, drawers, and deep links. Keep the route map, but design navigation around mobile history and platform controls.

Map each web URL to a native screen, then group related screens in native navigators. Expo Router uses file-based routes; React Navigation uses an explicit navigator tree.

  • app/page.tsx → a Home screen or Expo Router's app/index.tsx.
  • app/products/[id]/page.tsxProductDetails with an id, or app/products/[id].tsx.
  • Nested layout.tsx → a nested navigator or Expo Router _layout.tsx.
  • <Link href="...">navigation.navigate() or router.push().

Use push and pop actions for linear screen history. Put persistent destinations in tabs or a drawer instead of recreating the website's header menu.

Dynamic segments become route parameters. Read and validate id with useRoute() in React Navigation or useLocalSearchParams() in Expo Router before loading the screen.

// Expo Router: app/products/[id].tsx
const { id } = useLocalSearchParams<{ id: string }>();
if (!id) return <Text>Product not found</Text>;
return <ProductScreen productId={id} />;

Configure external entry points in four parts:

  1. Map each path, such as /products/:id, to its native screen.
  2. Register a custom scheme for app-to-app links.
  3. Associate the verified web domain with iOS Universal Links and Android App Links.
  4. Test cold starts, background opens, and links received while the app is active.

Use a reverse-domain custom scheme such as com.example.app://products/42, then map the same path to a verified HTTPS domain. Make that web URL fall back to a useful browser page when the app is not installed.

Move frontend redirects into conditional navigator rendering based on authentication state. Keep authorization and request-level access controls on the server; a hidden mobile screen is not a security boundary.

For a protected deep link, use this sequence:

  1. Parse the incoming URL and identify the requested screen and parameters.
  2. Open the destination immediately when the session is authenticated.
  3. Show the authentication stack when the session is unauthenticated, while retaining the requested destination.
  4. After sign-in, replace the authentication screen with the original destination.

Test the same flow from a terminated app and a backgrounded app. Back navigation should return to a valid screen rather than reopening the sign-in flow.

7. Separate Server Features

Separate every Next.js server feature into an authenticated backend endpoint before the mobile app calls it. Keep secrets and database work outside the React Native bundle.

Next.js can hide server work inside a page. A mobile app needs each responsibility to have a visible backend boundary:

  • React Server Component: Client component plus an API query.
  • Server Action: Authenticated REST, GraphQL, or RPC endpoint.
  • SSR data loader: Query on launch, focus, or refresh.
  • SSG page data: Bundled seed data or a cached API response.
  • ISR revalidation: Server cache invalidation followed by a client refetch.

A Server Action can hold permissions and side effects in one place. Record the contract before you turn it into an endpoint:

  • Action name: Identify the Server Action and every screen that calls it.
  • Database operation: Record which tables, records, or external services it reads or changes.
  • Authorization rule: State who can run the operation and which records they can access.
  • Endpoint contract: Define the REST, GraphQL, or RPC route. Document its request, response, and error schemas.
// Mobile API contract
type SaveProfileRequest = { displayName: string };

const body: SaveProfileRequest = { displayName };

await api.post('/profile', body, {
 headers: { Authorization: `Bearer ${token}` },
});

Keep validation on both sides of the boundary. Client checks improve feedback, while server validation protects the database from malformed or unauthorized requests.

Treat the mobile binary as public. Anyone can inspect a released app, so server API keys, database credentials, signing secrets, and service-account files must stay outside the React Native bundle.

The endpoint is the trust boundary between the app and your backend. Protect it with:

  • Authentication: Validate each session or JWT before running server code.
  • Authorization: Check ownership, role, or tenant access for every operation.
  • Input validation: Parse request bodies against an explicit schema before using them.
  • Rate limits: Throttle sensitive or expensive routes at the gateway.
  • Database rules: Apply row-level security where the database supports it.

Treat compiled values as public. Environment variables included in a mobile build are configuration, not secrets. Limit them to values such as an API base URL.

Mobile data fetching follows a refresh policy rather than a render-time mode. TanStack Query or SWR can refetch after launch, reconnection, foreground return, or session refresh.

Map each Next.js behavior deliberately:

  • SSR: Fetch private or frequently changing data when the screen needs it.
  • SSG: Bundle stable reference data with the app, or cache the API response locally.
  • ISR: Invalidate data on the server, then let the client refetch after its cache becomes stale.
  • Loading failures: Define retry limits and an offline fallback instead of waiting indefinitely.

Keep authoritative calculations on the server. The mobile cache should speed up reads without becoming the source of truth for permissions, balances, or shared records.

An adapter keeps provider details at the edge of the app. Put a typed interface between React Native code and each backend provider.

  • Define methods around application tasks, such as getProfile() or saveDraft().
  • Keep HTTP paths, headers, and provider response shapes inside the adapter.
  • Convert provider responses into application-owned types before returning data.
  • Inject the active adapter through configuration so tests can use a local implementation.

Avoid exposing raw database models through the interface. A narrow contract lets the server schema or API provider change without forcing the mobile code to change with it.

8. Adapt Data and Storage

Replace browser storage with native storage that matches each data type, its sensitivity, and the app's offline needs.

Storage determines what survives a restart, works offline, and stays protected on the device. Choose the layer by data shape, sensitivity, and offline behavior:

  • Small, non-sensitive preferences: Use a key-value store such as MMKV or AsyncStorage.
  • Queryable offline records: Use SQLite when data needs indexes, filtering, or transactions.
  • Temporary response data: Let TanStack Query or SWR manage cache lifetime and revalidation.
  • Credentials: Use iOS Keychain or Android Keystore through a secure-storage library.

Keep get, set, remove, and clearUserData behind one storage adapter. Handle serialization, schema migrations, and compatibility with older app versions there so storage changes do not ripple through business code.

export interface StorageAdapter {
  get(key: string): Promise<string | null>;
  set(key: string, value: string): Promise<void>;
  remove(key: string): Promise<void>;
  clearUserData(): Promise<void>;
}

Store authentication secrets in OS-protected credential storage. Keep access tokens in memory when the session design allows it, and place refresh tokens in iOS Keychain or Android Keystore.

  • Never write tokens, API keys, or credentials to AsyncStorage, SQLite, logs, analytics events, or crash reports.
  • Delete stored credentials on sign-out and when the server revokes the session.
  • Send tokens only over HTTPS and validate expiry, issuer, and audience on the server.
  • Rotate refresh tokens when your authentication service supports rotation.

Secure storage protects data copied from the device filesystem. Authorization checks, request throttling, and row-level security still belong on the backend because an attacker can call public endpoints without using your app.

Design offline storage as a cache plus a mutation queue. The server remains authoritative, while the device records pending writes and retries them after connectivity returns.

For each synchronized record, decide:

  • Cache policy: Define when data expires and whether stale reads are acceptable offline.
  • Mutation identity: Attach a unique idempotency key so retries do not create duplicate writes.
  • Conflict signal: Compare a version number or server timestamp before accepting an update.
  • Conflict rule: Choose server-wins, client-wins, field-level merge, or manual review for that record type.
  • Queue behavior: Preserve write order where operations depend on earlier changes.

After a successful sync, replace local records with the server response and invalidate related queries. If a long-offline edit conflicts, apply the chosen conflict rule and retain enough state to resume interrupted forms or uploads safely.

9. Add Native Platform Behavior

Add native platform behavior by mapping each required device capability to a user task and a tested permission flow on both platforms. Implement only the capabilities your product needs.

Prepare the migration around five capability groups, beginning with the user task each one supports:

  • Push notifications need clear triggers and a defined destination after a notification tap.
  • Camera and photo access require a choice between capture, library import, or both, plus a usable denied-access state.
  • Location access needs a foreground or background decision and a fallback for unavailable GPS data.
  • Payments and paywalls need product-to-entitlement mapping, restored purchases, and failed-transaction handling.
  • Biometric login should protect an existing signed-in session while leaving a passcode or password fallback.

Every capability follows the same permission lifecycle:

  1. Declare the capability. Add the required iOS and Android configuration with a plain explanation of why the app needs access.
  2. Request access in context. Ask when the user starts the related task, such as tapping a photo-upload control.
  3. Handle denial. Keep the screen usable, explain what is unavailable, and link to system settings when the operating system no longer shows the permission prompt.
  4. Test both platforms. Check initial approval, denial, and later revocation on iOS and Android because each operating system handles permission states separately.

Bilt can generate native React Native components and configure requested camera, GPS, and push permissions. You still define payment rules, verify purchase entitlements on the backend, and manage push tokens and message delivery server-side.

Capabilities that require custom platform code belong in the native module work covered in section 10.

10. Build Custom Native Modules

Build a custom native module only when no maintained React Native or Expo package exposes the native capability you need. Standard device features should stay on maintained packages.

Use this decision list before committing to native work:

  • Use a maintained package: It supports your React Native version, target platforms, required API surface, and release model.
  • Extend an existing package: Add a small native feature or Expo config plugin when the package is active and the change does not require a private fork. Use direct native work when build-time configuration is not enough.
  • Build a TurboModule: Use a typed JavaScript contract with Codegen and JSI when no maintained package exposes the capability. Keep synchronous calls small; return promises or emit events for longer work.
  • Reconsider the dependency: The vendor cannot supply supported native SDKs, licensing terms, or upgrade guidance.

Audit every proprietary dependency before implementation:

  • Confirm supported iOS and Android versions.
  • Record binary formats, transitive native libraries, and build settings.
  • Check whether the vendor permits redistribution inside App Store and Play Store builds.
  • Map the native methods, events, errors, and data types your JavaScript code needs.
  • Assign an owner for vendor updates, React Native upgrades, and device testing.

Implement the module through React Native's New Architecture:

export interface Spec extends TurboModule {
  openScanner(options: ScannerOptions): Promise<ScanResult>;
}
  1. Define the contract. Write a typed TypeScript specification for callable methods, return values, and events.
  2. Run Codegen. Generate the platform interfaces and registration scaffolding from the specification.
  3. Write native implementations. Use Swift or Objective-C for iOS, and Kotlin or Java for Android. Wrap shared C++ code where the dependency requires it.
  4. Add an adapter. Keep vendor-specific calls behind one application-facing interface so the rest of your state and UI do not depend on SDK details.
  5. Wire the builds. Add native dependencies and configuration to Xcode, Gradle, package metadata, and CI.
  6. Test the boundary. Cover invalid inputs, native errors, event delivery, app restarts, and real hardware behavior.

Custom modules create a permanent native maintenance obligation:

  • Rebuild and test after React Native, Xcode, Android Gradle Plugin, or vendor SDK upgrades.
  • Keep physical devices or accessories available in CI or a repeatable release test.
  • Track vendor deprecations and operating-system changes that affect the native API.
  • Document setup and recovery steps so the module does not depend on one developer's machine.

11. Test and Check Parity

A successful build proves that the mobile app compiles. Parity testing proves that one complete flow still behaves like the Next.js version across normal, stateful, and failure conditions.

Use the same short loop after adapting each route:

  1. Choose one flow. Start with a bounded journey such as sign in, password reset, or editing a profile. Write down its expected screens and outcomes.
  2. Establish the web baseline. Run the flow in the Next.js app and record visible states, validation rules, navigation results, and data changes.
  3. Compare the native preview. Run the same inputs in Bilt's integrated simulator. Compare every screen and transition with the web baseline, then move to physical hardware.
  4. Exercise state and failure cases. Repeat the flow with an expired session, denied permission, failed request, empty response, and an interrupted return to the app.
  5. Test both mobile platforms. Use Bilt's QR code to reach a physical iPhone and Android phone quickly, then repeat the same cases on each. QR access does not replace platform coverage.
  6. Log, fix, and repeat. Record each mismatch, fix the smallest failing behavior, and rerun the flow across every environment before adapting the next route.

Each environment catches different gaps:

  • Next.js web app: Expected behavior, validation, navigation, and data changes.

Add unit tests for shared logic, integration tests for API and storage adapters, and end-to-end tests for critical journeys. Measure startup, long-list scrolling, background recovery, font scaling, VoiceOver, and TalkBack in release builds.

  • Bilt native simulator: Fast checks for layout, interactions, and missing flow steps.
  • Physical iPhone: Safe areas, keyboards, permissions, screen readers, and touch targets.
  • Physical Android phone: Hardware back behavior, keyboards, permissions, screen readers, and touch targets.

Point mobile test builds at a separate staging backend rather than production. Use dedicated test accounts and disposable records so failed requests, migrations, and destructive actions cannot affect live customer data.

Observability closes the loop. Capture unhandled exceptions, failed network requests, authentication failures, and the route or action that triggered each event, then attach that evidence to the parity log.

Need broader hardware coverage? iOS and Android testing options distinguish automation frameworks from real-device clouds.

12. Sync or Export the Code

Move the completed React Native app into its own repository or export it for local development, review, and CI/CD.

Keep the original Next.js repository separate from the generated React Native repository. Use Bilt to sync the mobile code with GitHub or export the project for local development, code review, and CI/CD.

The repository structure should look like this:

  • nextjs-web/ → your existing Next.js application, retained as the web source and conversion reference
  • react-native-mobile/ → the separate React Native project generated by Bilt
  • Bilt ↔ react-native-mobile/ → the sync relationship between Bilt and the team-controlled mobile repository

With Bilt's full source code access, you own the generated mobile code. You can keep iterating in Bilt, continue in GitHub, or export the project for independent maintenance.

Decide who owns generated changes when Bilt and developers both edit the mobile project. Use reviewed branches, resolve merge conflicts before syncing, and tag known-good releases so you can roll back safely.

The export contains the standard React Native project structure:

  • Readable application source code
  • Images and custom icons
  • Typography files
  • Project configuration files

The exported app runs without proprietary Bilt formats or required Bilt dependencies. Apps using Bilt Payments or a Bilt-managed backend retain those service connections.

Use this export handoff checklist:

  • Confirm the Next.js and React Native projects are in separate repositories.
  • Clone the mobile repository and open it in VS Code, Cursor, or Xcode.
  • Review the source, assets, and configuration files before merging changes.
  • Connect the React Native repository to your normal pull-request and CI/CD workflow.
  • Document any Bilt Payments or managed-backend dependencies for the maintenance team.

13. Build and Publish

Use Bilt to create native iOS and Android builds and submit them through its publishing workflow. You still need active developer accounts and must complete each store's review requirements.

You can start the first native build in about two minutes. Signed build completion and store submission take longer.

A complete submission can take hours or days, depending on whether the build, store listing, privacy disclosures, and account setup are ready, plus each store's review process.

iOS publishing checklist

  1. Connect your Apple developer account to Bilt's App Store Connect workflow.
  2. Complete the listing metadata, review credentials, privacy disclosures, required usage descriptions, and any applicable privacy manifest details in App Store Connect. Check Apple's submission guidance before release.
  3. Generate the cloud build through Bilt, then confirm the distribution certificate and provisioning profile are in place.
  4. Test the signed build through TestFlight when beta testing is part of your release plan.
    1. Submit the finished build for review in App Store Connect through Bilt's managed submission workflow.

Android publishing checklist

  1. Connect the Google developer account that will own the app listing.
  2. Complete the package identifier, Play listing, review access, policy information, account-deletion details, and Data safety form in Google Play Console.
  3. Generate the production Android build through Bilt and confirm that app signing is configured.
  4. Submit the build through Bilt's publishing workflow and complete the store metadata in Google Play Console.
  5. Monitor Google Play review and answer any policy or content questions from the store.

You can review the full app store deployment workflow before starting a release.

Rejection triage

Store rejection means the flagged issue needs a response or a new build. Approval remains with Apple or Google.

  1. Read the exact rejection message and identify whether it concerns app behavior, metadata, or policy information.
  2. Provide the requested clarification or update the affected content in Bilt.
  3. Ask Bilt to rebuild the app when the fix changes code, configuration, or user flows.
  4. Resubmit the corrected build or metadata through the same managed workflow.
  5. Use Bilt’s live publishing support for Apple review issues that need guided troubleshooting.

Capacitor and PWA Alternative

A PWA fits when browser installation meets the distribution goal. Choose Capacitor for a thin iOS or Android shell around existing web UI; choose React Native for fuller native UI and deeper device behavior.

PWA implementation path

Keep the Next.js app on the web, then make it installable:

  1. Add a web app manifest with the app name, start URL, display mode, and theme colors.
  2. Supply correctly sized icons and connect the manifest to the app.
  3. Deploy over HTTPS so browsers can register the service worker.
  4. Define a service-worker strategy for static assets, updates, and offline behavior. Avoid caching authenticated pages or API responses without an explicit invalidation plan.
  5. Test installation and updates on the mobile browsers your customers use.

The PWA path described here distributes through the browser rather than a native store submission. It does not provide platform-managed in-app purchases or an App Store listing, so acquisition remains web-led.

Capacitor implementation path

Capacitor keeps the web interface and adds native iOS and Android projects around it:

  1. Add Capacitor to the project, create the native platforms, and point its web directory at the built web assets.
  2. Keep server-side Next.js logic on a hosted backend. Bundle only browser-ready assets; a remotely hosted site inside the shell has different offline behavior and may receive closer review than bundled assets.
  3. Sync each web build into the native projects whenever the interface changes.
  4. Configure plugins for required device behavior, such as camera access, notifications, or secure storage.
  5. Test the iOS project in Xcode and the Android project in Android Studio, then prepare icons, screenshots, privacy details, and store descriptions.

Apple review risk

Apple's App Store Review Guideline 4.2 requires enough functionality and lasting value to justify an app. A website placed inside a WebView can fail that test when the submission adds no meaningful mobile behavior.

Capacitor itself is not the problem. Build native value around the product's purpose, explain that value in the listing, and make every plugin-backed feature work during review.

Architecture remains the deciding factor: web wrappers versus native apps extends the trade-off to performance and device access.

Migration Completion Checklist

A completed Next.js-to-React-Native migration passes screen-parity checks, native permission and security checks, and store-submission requirements before production release.

Treat this checklist as the release gate. The React Native app is ready for production only when every applicable item passes.

Parity

  • Every screen completes the same core task as its Next.js counterpart, including empty, loading, error, and success states.
  • Text, spacing, controls, images, and responsive layouts match the approved designs on supported phone sizes.
  • Stack, tab, back, deep-link, and signed-out navigation land on the expected screen without relying on browser history.
  • Forms preserve validation rules, submitted values, error messages, and post-submit behavior.
  • VoiceOver and TalkBack labels, focus order, Dynamic Type, contrast, touch targets, and reduced-motion behavior pass on supported devices.

Native behavior

  • Push notifications open the correct destination from foreground, background, and closed states.
  • Camera and location permissions handle approval, denial, and later changes in device settings.
  • Biometric login falls back to the standard sign-in flow when biometrics fail or are unavailable.

[ ] Subscription purchase, cancellation, restoration, and paywall access pass on every supported store, where applicable.

  • Background return, expired-session refresh, interrupted forms or uploads, stale deep links, and queued offline writes recover safely.

Security and environments

  • Authentication tokens expire and refresh correctly, and signing out removes stored credentials from the device.
  • The client validates for usable feedback, the server validates against a schema, database calls use safe parameterized operations, and output is encoded for its destination.
  • API keys and secrets stay outside the mobile bundle; database row-level rules block unauthorized records.
  • Staging and production use separate credentials, databases, API endpoints, and release builds.
  • Type checks, unit tests, and release builds pass in CI before a binary is approved.
  • Release builds meet startup and scrolling targets, crash and network monitoring are active, and the team has a tested rollback path.

Release readiness

  • Developer accounts are active: Apple Developer Program membership is $99 per year, and Google Play Console registration is $25 one time.
  • Internal testing passes on supported iOS and Android devices.
  • Beta testers complete both a clean install and an upgrade from the current production version without losing account access or saved data.
  • Release-blocking beta findings are fixed, retested, and closed.
  • Store listings, screenshots, support details, privacy disclosures, review credentials, and purchase information match the shipped build.
  • Signed builds are submitted after beta approval, with version numbers and release notes confirmed.
  • A staged rollout begins after store approval; expand it only after authentication, crashes, and core workflows remain stable.

Turn Web Code Into Native Apps

Manual porting rebuilds screens, navigation, and platform behavior one piece at a time. With Web App Sync, Bilt uses your Next.js repository as context while creating and refining a separate React Native project.

You can compare flows in Bilt's native simulator, test on real devices, keep the generated React Native code, and run cloud builds for the App Store and Google Play.

If your Next.js app has complex authentication, server features, or native integrations, get a migration plan before you rebuild. Get expert mobile advice in a free 15-minute call.

FAQs

Can I reuse the same React components in Next.js and React Native?

You can share business logic, types, state stores, hooks, and components built with cross-platform primitives. Components tied to HTML, CSS, Next.js routing, or server rendering need mobile implementations.

Cross-platform primitives make component sharing possible, but you should still test layout, accessibility, and interaction on both mobile platforms.

Do I need native iOS or Android skills for React Native?

JavaScript or TypeScript and React cover standard screens, navigation, and supported APIs. Swift, Kotlin, Xcode, or Android Studio knowledge becomes necessary for unsupported SDKs, custom modules, and native build failures.

Managed cloud builds can handle routine signing and compilation without a local IDE, but they do not remove custom native-code requirements.

How long does this migration usually take?

Migration time depends on the number of screens, server features, native integrations, and parity gaps.

Parity testing and store review extend the full release timeline.

Estimate the release after auditing routes, shared logic, native features, and backend changes. Include physical-device testing and app-store review rather than treating the first successful build as the finish line.

Do I have to use Expo for this migration?

Expo is not required. You can use Expo's managed workflow, generate native projects with prebuild, or work directly with React Native CLI.

Expo fits when its SDK and configuration plugins cover your dependencies. Prebuild creates native projects when you need direct configuration or custom native work.

React Native CLI fits when you need direct Xcode and Android Studio control or custom native modules.