RLPv2

Retail Web Experiences · Architecture overview

One front door for every store app we build.

Retail Launchpad v2 (RLPv2) is a thin, stable frame that handles sign-in, store context, language and navigation, so app teams can ship independently inside it. This page walks through how it works and why it was built this way.

Simple Flexible Robust
0
stores active in the last 7 days
0
unique associates signed in, last 7 days
0→0
stores active per day, before vs. one week after the v1 → v2 rollout
0
of sign-in handoffs to embedded apps succeeded (36k+ last week)

01 · The mental model

Two things live inside one frame: apps and widgets.

Everything an associate sees is either a full app they open from the sidebar or a widget card on Home. The host is the frame around both. It is the only piece that knows who you are, which store you are in and what language you speak.

Retail LaunchpadStore 0129 · EN
Home
Inventory
Nike Cash
SOR
Electronic Journal
Store Hours
Store KPIs
SOR team
Variance alert
Shows only when there's a variance
Cash mode
Shows only when needed
iframe
App
Loaded from the app team's own deployment.
The host passes sign-in, store & language.
Host = the building
Security desk, address, lights and signage. Every room shares it.
Apps = the rooms
Each team furnishes its own room and can renovate without closing the building.
Widgets = the lobby board
Short notices that only go up when there's something to act on.

02 · Decision

Why apps are embedded with iframes

Iframes are the oldest and most battle-tested way to compose web apps. We picked them on purpose, because they fit the problem: many teams, many stacks, and an install base that was already running on RLP v1.

Iframes

Used for full-screen apps in the sidebar.

01

Every existing app already worked this way

RLP v1 embedded apps in iframes. Keeping the same model meant v2 could host every v1 app on day one, and app teams didn't have to rewrite anything to move.

02

Teams pick their own tools

An iframe only needs a URL. Inventory, Nike Cash, SOR, Electronic Journal and Store Hours are built and released by different teams on different schedules.

03

A problem in one app stays in that app

The browser keeps each iframe's code and styles separate. A crash or a bad release in one app can't break the host or any other app.

04

The browser enforces the security boundary

Apps can't reach into the host. They have to ask for what they need, and the host only answers apps on its approved list.

05

Independent releases

Teams ship to their own URL whenever they're ready. The host never needs a release for an app change.

The tradeoff we accepted: each app loads as its own page (median ~0.6s last week), and visual consistency depends on teams using the Nike design system. For full-screen tools that associates open deliberately, we judged that a good trade for isolation and zero-rewrite compatibility.
Live example · Store Operations Reporting

Separate code, separate deploys. Still feels like one app.

Store Operations Reporting (SOR) is built, hosted and released entirely by its own team, yet associates can't tell where the host ends and SOR begins. Two things make that work:

  • Shared design system. SOR uses the same Nike fonts, spacing and components as the host, so there's no visual seam.
  • Navigation that crosses the boundary. SOR tells the host which pages it has (KPI, Variance Report, Till Totals…), and the host shows them in its own sidebar. Clicking one moves SOR to that page, and the browser address bar and back button stay in sync.
Store KPI report from SOR, embedded inside the RLP v2 host with the host sidebar showing SOR sub-pages
Host
Host sidebar (SOR pages listed by SOR)
SOR, inside an iframe
Live production screenshot: the SOR KPI report embedded in RLP v2.

03 · How it works

How the host and an app talk to each other

The host and the app exchange short, named messages through the browser's built-in, secure messaging channel. It's a small, fixed vocabulary, about eight messages, and it hasn't changed since v1.

HostRLPv2
Appinside the iframe
Opens the app in a frame
URL comes from the host's app list, per environment
"Who's signed in?"
requestAccessToken
Host checks the app is on the approved list. Unknown pages get nothing.
Hands over a sign-in token
The app never sees a password or a login screen
"Which store? Which language?"
sendStore · getLanguage
Store number, country, language
One source of truth, set once at sign-in
"Update my title and URL"
setTitle · addressbar, so the browser tab, links and the back button keep working
Pushes a fresh token when it renews
Sessions continue without re-login
8 steps
LAST 7 DAYS

0

sign-in handoffs from host to app

HANDOFF SPEED (P95)

~3 ms

the host answers almost instantly once the app asks

FAILED HANDOFFS

4

out of 36,322 (0.01%), each one logged and alertable

04 · Backwards compatibility

Same apps, same language, new frame

RLP v2 speaks exactly the same message vocabulary as RLP v1 (rbh-retail-launchpad). To an embedded app, the two hosts look the same. That's what let us move stores from v1 to v2 without asking app teams to change code.

RLP v1 rbh-retail-launchpad

  • requestAccessToken
  • sendStore
  • getLanguage
  • setTitle
  • addressbar
  • getUrl
  • close
  • logout
InventoryNike CashSORElectronic JournalStore Hours
same apps, same URLs

RLP v2 rwe-core

  • requestAccessToken
  • sendStore
  • getLanguage + legacy format
  • setTitle
  • addressbar
  • getUrl
  • close
  • logout
14 → 546

Stores active per day went from 14 to 546 in one week after the Oct 20 rollout, and every v1 app came along unchanged. v2 also adds things v1 didn't have: native iOS sign-in, proactive token renewal, Home widgets and full telemetry.

05 · Widgets

Widgets decide for themselves when to show up

Widgets are Home cards built by app teams and loaded directly into the page, not in an iframe, so they feel native. The host doesn't hold any business rules. It loads each widget and then follows a simple contract: the widget says "show me" or "hide me."

1

Register

The host reads its widget list and loads each widget's code from the owning team's own deployment.

2

Decide

The widget checks its own data. Is there a variance today? Is the store in cash mode?

3

Show or stay hidden

The widget tells the host "ready" (show this card) or "hide." Home only shows what needs attention.

4

Remember dismissals

If an associate closes a card, the host remembers, until the widget has something new.

5

Watchdog

If a widget never reports back within 5 seconds, the host flags it in New Relic. If one fails to load, it just doesn't appear.

Home

Store KPIs
Persistent: always has something to show
loading
Variance alert
Found a variance, so it shows
loading
Cash mode
Not needed right now, so it hides
loading
Another widget
Failed to load: it never appears and the page carries on
loading

Why let the widget decide?

01

The host stays generic

No business rules live in the frame, so changing a rule ("alert when variance > X") never needs a host release.

02

The owners own the logic

The team that knows the data decides what's worth surfacing, and can change it on its own schedule.

03

Home stays uncluttered

Associates only see cards that need action, which keeps attention on real work.

04

Failures stay quiet

A widget that can't load or has nothing to say simply doesn't render. There are no broken tiles.

LOADED VS. ACTUALLY SHOWN · OCT 25–27

Widgets hold back unless they have something to say

LoadedShown

The Variance widget loaded 13,457 times but only showed itself 1,162 times (9%), because it only appears when there's a variance to act on. Widget failures over the same period: 8 out of 63,464 loads.

06 · Decision

Why widgets poll today, and why events come next

For the MVP, each widget checks its own API on a timer ("polling"). An event-driven model, where the API pushes changes the moment they happen, is the natural next step as we add widgets. We chose to sequence it, not skip it.

Today · MVP

Polling

Widgeton Home
Team APIexisting
"Anything new?"
"Not yet" … "Yes!"
  • Zero new infrastructure. Each widget reuses its team's existing API.
  • Teams stay independent. There's no shared event contract to negotiate across teams.
  • Right-sized. With three live widgets and data that doesn't need second-by-second freshness, polling is cheap and easy to reason about.
  • Fast to ship. It got real value onto Home while we learned what associates actually use.
Next · as widgets grow

Event-driven

Widgeton Home
Event sourcepushes changes
(quiet until something happens)
"Variance detected"
  • Scales with widget count. Requests no longer multiply by widgets × stores × timers.
  • Instant. Cards appear the moment the data changes, not on the next check.
  • The cost: a shared push channel and an event contract to design, run and monitor. That's worth paying once there are enough widgets to justify it.
No rewrite needed

Because widgets already own the show/hide decision, moving to events only changes how a widget hears about new data. The host contract ("show me" / "hide me") stays the same, so we can migrate one widget at a time.

07 · Robust by design

Simple doesn't mean fragile

The architecture contains failures by default, centralizes security, and gives us switches to react quickly without a deploy.

Failures stay contained

If an app is down, only its frame is affected. Navigation, sign-in and every other app keep working. Broken widgets simply don't render.

Last 7 days: 1 app-ready timeout out of 34,002 app loads

Sign in once, for everything

The host handles sign-in on web (Nike OAuth) and inside the iOS app (native bridge). Apps receive a token and never handle credentials.

Web sign-in success: 99.1% · iOS refresh failures: 19 out of 35k

Approved apps only

The host only answers messages from apps on its approved list. Every rejected attempt is logged as a security event we can alert on.

Rejected origins last 7 days: 0

Per-store kill switches

Every app can be turned off for a store through store configuration, with no deploy. That's useful for pilots, incidents and phased rollouts.

Configured for all 5 sidebar apps

Role-based access

Each app declares a minimum role level, so associates only see the tools their role allows.

Role levels from 20 (Inventory) to 40 (Store Hours)

Observable end to end

45+ named events cover sign-in, navigation, app loads, messages and widgets in New Relic, with a shared naming standard that every team uses.

See the live numbers in Proof
WEB · LAST 7 DAYS
0

associates on store computers and browsers

iOS APP · LAST 7 DAYS
0

associates using the same host inside the native iOS app

One codebase serves both. Nearly half of associates reach RLP v2 from the iOS app, with sign-in through the native bridge and no separate mobile build.

08 · Flexibility

What it takes to add something new

Adding an app or widget is a registration, not a project. The host doesn't change shape. It just learns about one more thing.

1

Build it your way

Any stack, hosted at the team's own URL. A v1 app already qualifies as-is.

2

Speak the 8 messages

Ask the host for sign-in, store and language. Apps built for v1 already do this.

3

Register it in the host

One entry: URL per environment, minimum role and kill switch. It then shows up in the sidebar.

09 · Proof

It's working, at scale, today

These are live production numbers from New Relic. Click through the tabs, and hover over the charts for detail.

10 · Build on it vs. start over

What starting over would throw away

A new platform would need to rebuild all of this before it delivered anything new, and then migrate the same 600+ stores a second time.

Already in production
  • Compatibility with every RLP v1 app8-message contract, 5 apps, no app rewrites
  • Sign-in on web and the native iOS appOAuth + native bridge + proactive token renewal
  • Store context, language, role gating, kill switchesPer store, no deploy required
  • Widget platform and lifecycleShow/hide contract, dismissal memory, watchdog
  • Observability standard45+ named events, shared SDK, alertable
  • Hosting and delivery pipelineStatic S3 + CloudFront, branch-based deploys
  • A completed v1 → v2 migration638 stores, 2,763 associates in the last week
A rewrite would mean
  • Re-proving compatibility with every app teamor asking them to rewrite their integrations
  • Rebuilding and re-certifying sign-inincluding coordination with the iOS app team
  • A second store migrationretraining, support load, change fatigue in stores
  • Losing our telemetry baselineno before/after comparison during the switch
  • Months of platform work before any new feature ships

11 · Roadmap

The next steps extend the platform; they don't replace it

Every item below fits inside the current architecture. Each one is additive and can roll out one app or one widget at a time.

Today
  • 5 sidebar apps via iframes, v1-compatible
  • 3 live Home widgets via federated modules
  • Widgets poll their own APIs
  • Web + native iOS from one codebase
Next
  • More widgets from more teams
  • Event-driven widget updates (API pushes changes), migrated one widget at a time
  • Alerts and dashboards as code on the existing telemetry
  • Better developer experience: shared libraries for building widgets and iframe apps, so teams ship faster with more consistency and reliability
Later
  • Optionally move iframe apps to federated modules as teams modernize, for a more native feel
  • Retire RLP v1 once the last stores move
  • Cross-widget events and personalization

No servers to run

Static files on S3, served globally through CloudFront.

Branch-based deploys

develop → test, main → production. Every PR is built and tested.

Independent teams

Apps and widgets deploy on their own schedules from their own repos.

Spec-driven changes

Significant changes are proposed, designed and reviewed as written specs before code.

Retail Launchpadv2

RLPv2 is a small, stable frame with clear contracts. It already carries 600+ stores on web and iOS, it brought every v1 app along unchanged, and it has a clear path to event-driven widgets without starting over. The fastest way to deliver new value to stores is to build on it.