Skip to content

WeWeb to React: Migration guide

WeWeb To React Guide

Start by understanding the export

A WeWeb export contains a runtime that reads routes, page JSON, collections, variables, workflows, plugins, sections, and generated elements. It does not look like a typical hand-written Vue app.

Converting each file to JSX can preserve the same generic state, scattered business logic, and tool-generated structure. Instead, use the export to document what the product does, then rebuild that behavior in React with clear modules for routing, data, forms, and business rules.

This guide uses the sprout-weweb example as a concrete reference, but the approach is designed to work for most WeWeb migrations.

What A WeWeb Export Contains

The sprout-weweb app assembles its pages at runtime. Most of its behavior is stored in configuration rather than route components.

The clearest evidence:

  • src/_front/router.js builds routes from window.wwg_designInfo, including page metadata, language handling, auth gating, and dynamic imports.
  • src/wwLib/services/wwWebsiteData.js fetches page JSON and injects pages, sections, collections, variables, formulas, workflows, and library components into runtime state.
  • src/pinia/variables.js acts as a generic variable system for website variables, plugin variables, component variables, query params, and local storage persistence.
  • src/_common/helpers/code/workflows.js is a generic workflow engine that executes actions like navigation, variable updates, collection fetches, conditional branches, popups, and plugin calls.
  • src/wwLib/services/wwCollection.js abstracts data fetching behind collection configuration and plugin adapters rather than a normal typed API layer.

So the export is closer to this:

flowchart LR
    wewebConfig[WeWebConfig]
    routerRuntime[RouteRuntime]
    pageData[PageJSON]
    variableRuntime[VariableRuntime]
    workflowRuntime[WorkflowRuntime]
    pluginAdapters[PluginAdapters]
    renderedApp[RenderedApp]

    wewebConfig --> routerRuntime
    wewebConfig --> pageData
    wewebConfig --> pluginAdapters
    pageData --> variableRuntime
    pageData --> workflowRuntime
    pluginAdapters --> workflowRuntime
    routerRuntime --> renderedApp
    variableRuntime --> renderedApp
    workflowRuntime --> renderedApp

Read that configuration to understand how the app works before writing its React replacement.

The Goal Of The Migration

Document the app’s behavior, group it by feature and responsibility, and rebuild it in modules your team can maintain. Copying the runtime into another framework leaves much of the original complexity in place.

The Sprout Example: What To Learn From It

The Sprout example is a good migration reference because it clearly shows the difference between application behavior and WeWeb implementation mechanics.

FUNCTIONALITY-ANALYSIS.md shows a 30-page product across several domains:

  • authentication
  • family portal
  • admin operations
  • reporting
  • commerce
  • calendar and scheduling
  • organization and location switching

That domain inventory is far more valuable for the migration than the generated component tree.

The most important recurring behaviors in the example are:

  • route-level private/public access
  • role-based redirects for admin pages
  • query-param-based page initialization
  • app-level initialization workflows
  • collection-driven data loading
  • popup-driven detail flows
  • calendar and date initialization
  • shopping cart and transaction behavior
  • organization/location context switching

Those are the things you must preserve.

The exact WeWeb-generated layout and element structure is mostly not worth preserving.

Separate Product Requirements From Generated Code

Every WeWeb concept should be categorized into one of two buckets.

Product Requirements

These must survive the migration:

  • business entities
  • user roles and access rules
  • routes and user flows
  • data dependencies
  • form submissions and validation rules
  • user-visible states
  • side effects like redirects, notifications, uploads, or policy signatures

WeWeb Implementation Artifacts

These usually should not survive as-is:

  • generic workflow graphs
  • generic variable registry
  • collection IDs as a frontend abstraction
  • generated sections as long-term component boundaries
  • page JSON as a permanent UI composition model
  • plugin-specific runtime conventions when a simpler service boundary is possible

How To Read A WeWeb App Before Rebuilding It

Use the export as a behavioral map.

1. Start With The Page Inventory

In Sprout, FUNCTIONALITY-ANALYSIS.md is already a good page index. If you do not have this document, derive it from page data.

For every page, capture:

  • route
  • access model
  • purpose
  • primary user actions
  • key forms
  • workflows triggered on load or submit
  • upstream data dependencies
  • downstream navigation effects

This gives you the product surface area.

2. Identify Repeated App-Level Patterns

In Sprout, repeated patterns matter more than one-off page details.

Examples:

  • admin permission enforcement across several pages
  • query-driven initialization for reports and reset-password flows
  • redirect pages used for organization and location changes
  • shared navigation/sidebar structure
  • popups used to display details without leaving the page

Build shared React components and functions for these repeated patterns.

3. Trace The Runtime Entry Points

In Sprout, the runtime entry points are more important than individual generated components.

Use these files first:

  • src/_front/router.js
  • src/wwLib/services/wwWebsiteData.js
  • src/_common/helpers/data/index.js
  • src/_common/helpers/code/workflows.js
  • src/wwLib/services/wwCollection.js
  • src/pinia/variables.js

Together, these explain:

  • when page state resets
  • when auth is checked
  • when variables are initialized
  • when workflows run
  • when collections fetch
  • when navigation happens

These files show when the app runs each part of its logic.

4. Convert Runtime Concepts Into A Domain Map

Before writing any React code, rewrite the system in domain language.

For example:

  • “collection 476bb250-...” should become “current organizations data” or “family transaction list”
  • “workflow 14ae93a6-...” should become “admin access guard”
  • “query variable” should become “URL-owned filter state”
  • “open popup action” should become “detail modal flow”

If the React migration keeps speaking in WeWeb IDs and generic action types, the architecture is still too close to the old system.

How To Translate WeWeb Concepts Into React Concepts

Use this mapping when deciding where each part belongs.

WeWeb conceptWhat it meansReact target
PageUser-facing route or screenRoute module plus feature module
SectionReusable layout/content fragmentLayout component or domain component
ElementUI primitive or widgetDesign-system primitive or feature component
WorkflowImplicit event/state graphExplicit route logic, service logic, mutation handler, or UI event handler
CollectionData source or queryTyped API function plus server-state query
VariableMixed state containerURL state, form state, local UI state, persisted preference, or session state
PluginIntegration/runtime adapterService client, SDK wrapper, or infrastructure module
FormulaDerived logic or expressionTypeScript utility, selector, formatter, or domain rule

This mapping matters because it prevents you from re-implementing WeWeb instead of building an app.

The Target React Architecture

Give each module a defined responsibility. The following structure is one starting point.

src/
  app/
    router/
    providers/
    layouts/
    guards/
  features/
    auth/
    families/
    students/
    activities/
    reports/
    commerce/
    settings/
  entities/
    user/
    organization/
    class/
    transaction/
  shared/
    ui/
    forms/
    api/
    lib/
    config/
    types/
  pages/
    login/
    dashboard/
    admin-families/
    family-profile/
    reports-financial/

You can vary the exact folder names, but the ownership model should stay stable.

App Layer

Owns:

  • routing
  • providers
  • layout shell
  • auth boundary
  • global error handling
  • app bootstrap

This is where private route enforcement and cross-cutting providers live.

Feature Layer

Owns:

  • domain workflows
  • route loaders/actions
  • query hooks
  • mutations
  • screens for a bounded area

For Sprout, this means separate feature modules for auth, reports, activities, commerce, and family management.

Shared Layer

Owns:

  • API clients
  • common UI
  • form abstractions
  • schema utilities
  • formatting and generic helpers

Keep feature-specific business logic out of shared modules.

Entity Layer

Owns:

  • core business types
  • reusable selectors
  • small domain utilities

Use it when several features depend on the same core object model.

For most migrations, these are strong defaults:

  • React Router for route modules and data-aware routing
  • TanStack Query for server state and mutation lifecycle
  • React Hook Form plus Zod for form state and validation
  • a typed API client layer for Xano or the replacement backend
  • a minimal client-state solution only for truly local or app-shell state
  • a proper component library or internal design system for UI primitives

Whichever libraries you choose, keep their responsibilities separate.

What Not To Recreate In React

Avoid rebuilding these WeWeb patterns in React:

1. A Giant Generic Variable Store

src/pinia/variables.js mixes:

  • query parameters
  • local storage
  • website variables
  • plugin variables
  • component variables
  • mutable objects and arrays

In React, these should be split by ownership:

  • URL state belongs in the router or search params
  • server state belongs in query/mutation caching
  • form state belongs in the form library
  • local UI state belongs in components
  • persistent preferences belong in a small persistence abstraction
  • session/auth state belongs in an auth module

A single global store would keep these different kinds of state mixed together.

2. A Workflow Interpreter

src/_common/helpers/code/workflows.js is useful for understanding behavior, but it is not a good target architecture.

That file currently handles:

  • control flow
  • navigation
  • data fetching
  • state mutation
  • uploads
  • popups
  • plugin calls
  • language changes
  • logging

In React, these responsibilities should be pushed into explicit modules with names that reflect business intent.

Instead of:

  • “execute workflow 14ae93a6-...”

You want:

  • requireAdminAccess()
  • initializeFinancialReportFiltersFromUrl()
  • switchOrganizationAndRedirectHome()
  • submitClassCreationForm()

3. A Collection Abstraction As Your Main Data Model

src/wwLib/services/wwCollection.js is a runtime-oriented abstraction that fetches through plugin adapters and configuration bindings.

That is acceptable inside a no-code runtime. It is not how you should design a long-term React frontend.

Your React app should prefer:

  • typed request functions
  • route loaders where appropriate
  • query hooks with stable keys
  • mutations with clear invalidation rules
  • domain names instead of collection IDs

Rebuilding Data The Right Way

A WeWeb app often blurs the line between server state and client state. Your React app should make that distinction explicit.

Server State

This includes:

  • current user
  • organizations and locations
  • families
  • activities
  • reports
  • transactions
  • class details
  • policy content

Server state should be fetched, cached, invalidated, and refetched by server-state tooling.

In Sprout, collection fetching currently happens during runtime initialization and workflow execution. In React, that should become route loaders or query hooks close to the consuming feature.

Client State

This includes:

  • open modal
  • selected tab
  • current calendar view mode
  • optimistic draft state
  • temporary filters not worth URL persistence

Keep this local by default.

URL State

Sprout uses query-driven initialization in several places:

  • reset password
  • report date ranges
  • view mode
  • duplication flows
  • embed filters

That is a strong signal that these values belong to the URL in the new app too.

Do not hide them in a store.

Persistent Preference State

If a value must persist between navigations or sessions, isolate that behavior in a small module:

  • theme
  • selected organization
  • selected location
  • last-used view mode

Do not mix this with business entities.

Rebuilding Routing And Navigation

src/_front/router.js is one of the most useful files in the Sprout export because it shows how the app currently thinks about routing.

It reveals:

  • routes are generated from window.wwg_designInfo
  • auth checks happen at route-entry time
  • page modules are dynamically imported
  • page data is fetched before render
  • language and hash behavior are part of routing

In React, make routing explicit.

Build route modules for stable business routes:

  • /login
  • /signup
  • /home
  • /admin/families
  • /admin/families/:familyOrganizationId
  • /activity-calendar
  • /financial-reports
  • /class-detail/:classId

Each route module should own:

  • route-level access requirements
  • route params parsing
  • loader logic when required before render
  • route-local error boundaries when needed

Auth And Permission Checks

In Sprout, auth and permission logic is split between route metadata, auth plugin state, and workflows.

In React, consolidate this into one access system:

  • auth provider or session boundary
  • permission model
  • route guard or loader enforcement

A good rule:

  • authentication decides whether the user may enter the private area
  • authorization decides which feature areas the user may access
  • feature code should not repeatedly reinvent redirect logic

Redirect Pages

Sprout uses special redirect pages for organization and location changes.

In React, those flows can become explicit mutation or command handlers:

  • update active organization
  • invalidate dependent queries
  • navigate to the intended landing page

You may still keep a dedicated transition route if it improves UX, but it should be a conscious product decision, not a byproduct of the old runtime.

Rebuilding Auth Properly

In the Sprout example, auth is spread across:

  • route checks in router.js
  • Xano auth plugin state
  • app-level workflows like logout on invalid account
  • organization and location initialization

That spread is normal in a no-code runtime. It is not ideal in a codebase.

Better React Auth Boundary

Create one auth module that owns:

  • session retrieval
  • token storage strategy
  • current user bootstrap
  • invalid session handling
  • unauthorized redirects
  • role and permission evaluation

It should expose explicit concepts:

  • isAuthenticated
  • currentUser
  • permissions
  • activeOrganization
  • activeLocation

Do not force feature modules to know how tokens or plugin state work.

Handle Multi-Context Auth Cleanly

Sprout clearly has organization and location context. That means auth is not just “logged in or not.”

You likely need:

  • session context
  • organization context
  • location context
  • permission matrix derived from user plus context

This should be modeled explicitly in TypeScript types and selectors.

Rebuilding Forms Instead Of Translating Them

WeWeb forms often hide logic inside variables, workflows, and component state. That does not mean your React version should.

Every Important Form Should Be Explicit

For each form, define:

  • schema
  • default value source
  • submission contract
  • validation rules
  • success path
  • error handling
  • optimistic or loading behavior

Examples from Sprout:

  • login
  • signup
  • reset password
  • family profile editing
  • class creation
  • report filters
  • cart and checkout actions

For each form:

  1. derive defaults from route loader, query params, or fetched server state
  2. validate with a schema
  3. submit through a typed mutation
  4. update cache intentionally
  5. navigate or notify based on a defined success policy

This is much clearer than a workflow graph that mutates generic variables and then changes page conditionally.

Rebuilding UI Composition And The Design System

Generated sections are helpful for understanding layout patterns, but they are rarely the right long-term component boundaries.

In Sprout, recurring sections include:

  • sidebar navigation
  • loaders
  • branding
  • headers
  • modal containers

Those should become intentional design-system or shell-level components.

A Better UI Hierarchy

Use three levels:

1. Primitive UI Components

Examples:

  • buttons
  • inputs
  • selects
  • modals
  • tables
  • badges

2. App Shell Components

Examples:

  • sidebar
  • top navigation
  • page container
  • authenticated layout
  • section header

3. Feature Components

Examples:

  • family list panel
  • student profile card
  • report date range toolbar
  • transaction ledger table
  • class schedule panel

This hierarchy gives you components that reflect the product, not the no-code builder.

How To Translate Workflows Into Explicit Logic

Use each workflow to understand the behavior you need to recreate.

For each workflow, ask:

  • is this route-entry logic?
  • is this coordination between features?
  • is this UI event handling?
  • is this just derived state?
  • is this a side effect triggered by a mutation?

Then move it to the right place.

Workflow Translation Patterns

Pattern 1: Auth Redirects

Current WeWeb form:

  • route metadata plus workflow branch plus change-page action

React form:

  • route guard or loader
  • central auth redirect helper
  • permission-aware layout boundary
Pattern 2: Query-Based Initialization

Current WeWeb form:

  • query variable plus before-collection-fetch workflow

React form:

  • parse search params in route or feature hook
  • normalize into typed filter state
  • pass into query keys or form defaults
Pattern 3: Popup Detail Flow

Current WeWeb form:

  • fetch collection, then open popup

React form:

  • selected entity ID in URL or local state
  • query for detail data
  • modal component with explicit props and loading states
Pattern 4: Redirect Pages

Current WeWeb form:

  • enter page, run workflow, navigate away

React form:

  • explicit command handler or transition route with a narrow purpose
Pattern 5: Calendar Initialization

Current WeWeb form:

  • on-load workflow sets date variables and view state

React form:

  • route loader for required data
  • derived initial state from URL or defaults
  • feature hook that coordinates date range and filters

Where To Put Each Type Of Logic

When you discover a workflow or variable, classify it using this matrix.

If the logic does thisPut it here
controls page accessroute guard or route loader
loads server dataquery hook or route loader
updates server datamutation handler or feature service
controls URL filterssearch params parser plus URL sync
drives only one componentlocal component state
is shared domain behaviorfeature service or domain utility
opens or closes overlayslocal UI state or shell-level modal manager
transforms fetched data for displayselector or pure utility

Migrate One Feature At A Time

Do not migrate page JSON, then sections, then elements, then workflows, one layer at a time. That produces a React app that mirrors the no-code runtime.

Migrate complete features instead.

A Good Order For A Real Project

1. App Shell And Auth

Build:

  • router
  • auth boundary
  • layouts
  • session bootstrap
  • permission checks

This becomes the foundation for every other slice.

2. One Representative Private Feature

Pick a route area with real depth, such as:

  • family profile
  • class detail
  • activity calendar

This forces you to prove your patterns for routing, data, forms, and UI.

3. One Admin Slice

Pick something like:

  • admin families
  • financial reports
  • cancellation reviews

This validates permission boundaries and more complex workflows.

4. Shared Cross-Cutting Systems

Once patterns are proven, extract:

  • table patterns
  • modal patterns
  • filter bars
  • form building blocks
  • query key conventions
5. Commerce And Edge Flows

Then handle:

  • cart
  • transactions
  • checkout-like interactions
  • embed flows
  • organization/location switching

That order reduces architectural rework.

How To Handle Xano And External Integrations

Sprout uses Xano through WeWeb plugins. In the migration, Xano should become an explicit backend integration boundary.

Prefer:

  • typed API clients
  • request/response schemas
  • centralized auth header handling
  • narrow service modules per feature

Avoid:

  • plugin-shaped abstractions leaking into feature code
  • generic “run backend action” helpers everywhere

If you later replace Xano, your React app should not need a full rewrite of feature modules.

Anti-Patterns That Will Hurt The Migration

Anti-Pattern 1: Keeping WeWeb IDs In Application Logic

Use names that describe the data and behavior instead of opaque collection and workflow IDs.

Anti-Pattern 2: Building A React No-Code Runtime

If you end up with:

  • generic action registries
  • giant config-driven flow execution
  • one universal state registry
  • dynamic rendering for everything

you have rebuilt the old problem in a different framework.

Anti-Pattern 3: Preserving Generated Component Boundaries

Generated sections are not automatically good component boundaries.

Use them as clues, not as the final structure.

Anti-Pattern 4: Mixing Entity Data With UI State

This usually happens when teams port variable-based logic too literally.

Keep domain data, route state, and UI state separate.

Anti-Pattern 5: Treating Migration As Pure QA Parity

Parity matters, but parity alone is not enough.

You also need:

  • maintainability parity
  • observability parity
  • type safety improvements
  • testability improvements

Otherwise the migration cost will not pay off.

A Practical Architecture For The Sprout-Type App

If I were rebuilding the Sprout example, I would target something like this:

flowchart TD
    browser[Browser]
    router[ReactRouter]
    authBoundary[AuthBoundary]
    layouts[AppLayouts]
    features[FeatureModules]
    queryLayer[ServerStateLayer]
    apiLayer[TypedAPILayer]
    xano[XanoBackend]

    browser --> router
    router --> authBoundary
    authBoundary --> layouts
    layouts --> features
    features --> queryLayer
    queryLayer --> apiLayer
    apiLayer --> xano

Supporting rules:

  • route loaders handle access and route-owned initialization
  • TanStack Query owns fetched entities and cache invalidation
  • feature modules own mutations and business workflows
  • forms own their own schemas and submission pipelines
  • shell state stays outside business entities
  • shared UI primitives are separated from feature widgets

Production-Ready Fundamentals The New App Should Have

Explicit Boundaries

Everyone should know where to put:

  • routing logic
  • auth logic
  • data fetching
  • form logic
  • coordination between features
  • UI primitives

Typed Contracts

The new app should have typed contracts for:

  • API requests
  • API responses
  • route params
  • search params
  • domain entities
  • form schemas

Observable Behavior

You should be able to inspect:

  • failed queries
  • failed mutations
  • auth state transitions
  • navigation failures
  • important business events

Testable Domain Flows

Critical paths should be testable without emulating a workflow engine:

  • login
  • permission redirects
  • class creation
  • report filtering
  • cart updates
  • organization switching

Design System Discipline

Reuse shared components instead of creating a separate version for every page.

Questions To Check Your Migration

For every piece of WeWeb logic, ask:

  1. what user-facing behavior does this create?
  2. what business rule is hiding behind it?
  3. who should own that rule in a normal React app?
  4. can I implement that rule without recreating the WeWeb runtime?

If the answer to the last question is no, you are still too close to the old architecture.

Check the result against the original app

Use the WeWeb export to understand the product’s behavior, data dependencies, business rules, and user flows. Rebuild those in React, then check that the new code is organized, testable, and understandable to the team that will maintain it.

More articles.

  1. How to bring generative AI into your company: a 90-day plan

    A practical 90-day plan for generative AI: pick the first process, assign owners, choose tools, run a checked pilot, manage risks, avoid stalling.

  2. What is an AI agent? A plain-English guide for business teams

    What an AI agent is, how it differs from a chatbot and an AI workflow, what it’s made of, and where it’s worth using in an ordinary company.

  3. What is MCP? The Model Context Protocol explained for companies

    What the Model Context Protocol is, how it connects AI apps to your CRM, files and databases, when you need it and what to secure first.

All articles

Want a second opinion on your project?

Book a call and we’ll talk through your options.

JurajCEO, QikBuild

You leave the call with

  • What we would build first
  • A rough price range
  • Whether custom software makes sense for you
Book a 30-minute call

Or write to hello@qikbuild.com