How JobKorea Adopted Server-Driven UI to Ship UI Changes Without App Releases

Korea's largest recruiting platform built a Screen/Section SDUI architecture that turned 3-day app store waits into instant UI updates — and their approach has lessons for every mobile team.

JobKorea (잡코리아) and its sister platform Albamon are Korea's dominant recruiting platforms. Together, they serve millions of job seekers and employers — from fresh graduates hunting their first gig to senior professionals navigating career transitions.

In February 2026, their engineering team published a detailed breakdown of how they adopted server-driven UI to solve a problem that will sound painfully familiar to any mobile team: the product team wants a change tomorrow, but the app store says "minimum 3 days."

What they built is one of the cleanest SDUI architectures we've seen in production — and it didn't require a thousand-person platform team to pull off.

The 3-Day Problem

"기획팀에서 '내일까지 홈 화면에 추천 공고 섹션을 추가해주세요'라는 요청이 올 때마다 돌아오는 대답은 항상 같았습니다. '앱은 스토어 심사가 있어서 최소 3일은 걸립니다.'"

"Every time the planning team asked us to add a recommendation section to the home screen by tomorrow, the answer was always the same: 'App store review takes at least 3 days.'"

— JobKorea Engineering Blog, February 2026

Here's what adding a single recommendation section to the home screen actually required:

In the recruiting industry, timing matters. Job postings are seasonal. Hiring surges happen fast. By the time a UI change went through the full release cycle, the moment had often passed.

3+ days minimum for any UI change
same work across iOS, Android, Web
Minutes time to ship with SDUI
~100B version check per screen load

Why They Chose SDUI Over Alternatives

JobKorea considered the usual options before landing on server-driven UI:

WebViews? They'd lose the native feel. In a market where competing job apps feel snappy and native, embedding web content for core screens would be a noticeable UX downgrade. Users in Korea have particularly high expectations for app quality.

Feature flags? Great for toggling features on and off, but they can't rearrange the home screen, add new section types, or change the layout itself. The "what" of the UI is still hardcoded.

More frequent releases? Even daily releases wouldn't solve the app store review bottleneck. And coordinating iOS + Android + Web for every change is expensive regardless of cadence.

Server-driven UI was the only approach that addressed all three constraints at once: instant updates, native rendering, and a single source of truth for the UI across platforms. This is the same conclusion companies like Nubank, Airbnb, and Netflix have reached — though each at different scales and with different architectures.

The Screen/Section Architecture

JobKorea's SDUI system is built on an elegantly simple two-tier model: Screens and Sections.

Screens: The Page Container

A Screen is the top-level definition of a full page in the app. But it's not just "the home page" — it's parameterized across five dimensions:

Screen Targeting Dimensions

  • siteType — Which platform: JOBKOREA or ALBAMON
  • deviceType — What device: MOBILE_APP, PC, or MOBILE_WEB
  • screenType — Which page: HOME, SEARCH_JOB, etc.
  • segmentType — User segment: GENERAL, NEWBIE, JUNIOR, SENIOR
  • testType — A/B test group assignment

This means the same "home screen" can have completely different layouts for a new graduate on the JobKorea mobile app versus a senior professional on Albamon's desktop site — all without any client-side branching logic.

Screen Configuration (JSON)
{
  "screen": {
    "siteType": "JOBKOREA",
    "deviceType": "MOBILE_APP",
    "screenType": "HOME",
    "segmentType": "NEWBIE",
    "testType": "GROUP_A",
    "version": "20260219123045",
    "sections": [
      { "sectionId": "HERO_BANNER", "order": 1 },
      { "sectionId": "JOB_RECOMMENDATION", "order": 2 },
      { "sectionId": "TRENDING_KEYWORDS", "order": 3 },
      { "sectionId": "RECENT_VIEWED", "order": 4 },
      { "sectionId": "SALARY_INSIGHTS", "order": 5 }
    ]
  }
}

Sections: Reusable UI Blocks

Sections are where the actual UI lives. Each section is a self-contained block with three key properties:

  1. A renderType that tells the client how to display it (CAROUSEL_CARD_SALARY, LIST_CARD_DEFAULT, GRID_CARD_THUMBNAIL, etc.)
  2. Its own apiUrl for loading data independently
  3. Complete isolation from other sections — one failure doesn't cascade
Section Definition (JSON)
{
  "section": {
    "sectionId": "JOB_RECOMMENDATION",
    "renderType": "CAROUSEL_CARD_SALARY",
    "title": "추천 공고",
    "apiUrl": "/api/v1/recommendations?segment=NEWBIE",
    "refreshInterval": 300,
    "fallback": {
      "renderType": "LIST_CARD_DEFAULT",
      "apiUrl": "/api/v1/popular-jobs"
    }
  }
}

The critical insight here: sections are reusable across screens. The same JOB_RECOMMENDATION section can appear on both the HOME screen and the SEARCH_JOB screen. Define it once, use it everywhere.

Screen/Section Reuse Model
📱 HOME Screen (Newbie)
HERO_BANNER BANNER_FULL
JOB_RECOMMENDATION ★ CAROUSEL_CARD_SALARY
TRENDING_KEYWORDS GRID_CHIP
SALARY_INSIGHTS LIST_CARD_DEFAULT
🔍 SEARCH_JOB Screen
SEARCH_BAR INPUT_SEARCH
FILTER_CHIPS GRID_CHIP
JOB_RECOMMENDATION ★ CAROUSEL_CARD_SALARY
SEARCH_RESULTS LIST_CARD_DEFAULT

★ Same section reused across different screens — defined once, rendered everywhere

This is a textbook application of the component-based SDUI pattern, but with an unusually clean separation between page structure (Screen) and reusable content blocks (Section).

How the Data Flows

JobKorea's data pipeline from admin configuration to user screen follows four steps:

Data Flow: Configuration → Rendering
Admin Tool
Configure screens & sections
Publish
Versioned JSON
Redis
Cached config store
Version Check
~100 bytes
Client Render
Sections load independently

Step 1: Admin configures. A product manager uses the internal management tool to define screens, assign sections, set targeting parameters, and arrange the layout. No engineering required.

Step 2: Publish. When ready, the admin publishes the configuration. A versioned JSON snapshot is created with a timestamp-based version string (e.g., "20260219123045").

Step 3: Redis cache. The published configuration is stored in Redis for fast retrieval. The version string acts as an ETag — clients can check if they need to download a new config without pulling the full payload.

Step 4: Client rendering. The mobile app checks the screen version on each load. If the version hasn't changed, it uses its cached configuration. If it has, it downloads the new config and renders each section independently, with each section fetching its own data from its apiUrl.

Client Version Check Flow
// Client-side pseudocode
const localVersion = cache.getVersion("HOME_SCREEN");
const remoteVersion = await api.getScreenVersion({
  siteType: "JOBKOREA",
  deviceType: "MOBILE_APP", 
  screenType: "HOME",
  segmentType: user.segment,
  testType: user.testGroup
});

if (localVersion !== remoteVersion) {
  // Download new configuration (~2-5KB)
  const screenConfig = await api.getScreenConfig(remoteVersion);
  cache.save("HOME_SCREEN", screenConfig);
}

// Render each section independently
for (const section of screenConfig.sections) {
  renderSection(section); // Each section loads its own data
}

The elegance here is in the economics: the version check is roughly 100 bytes. For the vast majority of screen loads, the UI configuration hasn't changed, so the app pays almost nothing in network cost.

Built-in A/B Testing

Most teams bolt A/B testing on after the fact — feature flags that toggle between code paths, third-party tools that add latency, or configuration systems that were never designed for experimentation.

JobKorea built it directly into the Screen model via the testType dimension:

// Same page, different experiences — zero code changes
{
  "screen": {
    "siteType": "JOBKOREA",
    "deviceType": "MOBILE_APP",
    "screenType": "HOME",
    "segmentType": "NEWBIE",
    "testType": "GROUP_A",  // ← Variant A
    "sections": [
      { "sectionId": "JOB_RECOMMENDATION", "order": 1 },
      { "sectionId": "COMPANY_SPOTLIGHT", "order": 2 }
    ]
  }
}

// vs.

{
  "screen": {
    "siteType": "JOBKOREA",
    "deviceType": "MOBILE_APP",
    "screenType": "HOME",
    "segmentType": "NEWBIE",
    "testType": "GROUP_B",  // ← Variant B
    "sections": [
      { "sectionId": "COMPANY_SPOTLIGHT", "order": 1 },
      { "sectionId": "JOB_RECOMMENDATION", "order": 2 },
      { "sectionId": "TRENDING_SEARCHES", "order": 3 }
    ]
  }
}

Group A sees recommendations first, then company spotlights. Group B gets the reverse order plus a trending searches section. Testing completely different page layouts — not just button colors — without touching a line of client code.

This is exactly the kind of deep experimentation capability that makes the business case for SDUI so compelling. When your product team can test structural changes to the home screen in minutes instead of release cycles, the pace of learning accelerates dramatically.

Resilience: Independent Section Loading

Here's where JobKorea's architecture really shines from a reliability perspective.

Because each section has its own apiUrl and loads data independently, a failure in one section is completely isolated from the rest of the screen:

What Happens When a Section Fails?

  • The recommendation API goes down → the recommendation section shows an error state or falls back to a simpler renderType
  • The rest of the home screen loads normally — hero banner, trending keywords, salary insights all render independently
  • Users might not even notice the degradation unless they're specifically looking for recommendations

This is a massive improvement over monolithic API designs where the home screen makes one big request and a failure anywhere returns nothing. With section-level isolation, your blast radius for any single backend issue is limited to one rectangular block on the page.

The section definition even supports explicit fallbacks:

{
  "section": {
    "sectionId": "JOB_RECOMMENDATION",
    "renderType": "CAROUSEL_CARD_SALARY",
    "apiUrl": "/api/v1/recommendations",
    "fallback": {
      "renderType": "LIST_CARD_DEFAULT",
      "apiUrl": "/api/v1/popular-jobs"
    }
  }
}

If the personalized recommendation API fails, the section automatically falls back to showing popular jobs in a simpler layout. The user still sees content — just a less personalized version of it.

Caching Strategy

JobKorea's caching is version-based and impressively lightweight:

  1. Version string: Each published screen configuration gets a timestamp-based version like "20260219123045"
  2. Lightweight check: On each screen load, the client sends the current version to the server (~100 bytes round trip)
  3. Download only if changed: If the version matches, no further data is transferred. If it's different, the client downloads the new screen config
  4. Local cache: The downloaded configuration is cached on-device until the next version change

In practice, screen configurations don't change that often — maybe a few times per week for the home screen, less for others. That means 99%+ of screen loads cost only ~100 bytes for the version check. No polling, no WebSocket connections, no background sync.

💡 Why this matters for performance: Many SDUI implementations download the full UI configuration on every screen load, adding latency. JobKorea's version-based approach means the SDUI layer adds virtually zero overhead to the normal app experience — you're paying 100 bytes to check if anything changed. That's smaller than a single tracking pixel. For more on SDUI performance patterns, see our guide on SDUI performance optimization.

What We Can Learn from JobKorea

JobKorea's implementation validates several key principles for any team considering SDUI:

1. You Don't Need a Massive Team

Unlike Nubank's 3,000-engineer operation, JobKorea built a production SDUI system with a much smaller team. Their architecture is simpler — no custom interpreter, no scripting engine — and that's a feature, not a limitation. Simpler systems are easier to build, maintain, debug, and extend.

2. Two Tiers Is Enough

The Screen/Section model is powerful in its simplicity. Screens define what sections appear and in what order. Sections define how data is rendered. That's it. No deeply nested component trees, no recursive layouts, no Turing-complete server expressions. For the vast majority of SDUI use cases — content-heavy screens with mix-and-match blocks — this is all you need.

3. Build Targeting Into the Architecture

Rather than bolting on personalization and A/B testing as afterthoughts, JobKorea made them first-class dimensions of the Screen model. The segmentType and testType fields are right there in the primary key. This is a crucial design decision that pays dividends as the product team's experimentation needs grow.

4. Independent Data Loading Is Non-Negotiable

Each section owning its own API endpoint is what makes the whole system resilient. It also enables sections to have different refresh intervals — a trending keywords section might refresh every minute, while salary insights refresh daily. This is a pattern we see in every successful SDUI implementation.

5. Version-Based Caching Keeps It Fast

The 100-byte version check pattern is elegant and efficient. It proves that SDUI doesn't have to mean "download your entire UI definition on every screen load." Smart caching makes SDUI invisible to the user.

How Pyramid Gives You This Architecture Out of the Box

JobKorea built something impressive — but they also built it from scratch. The admin tool, the Redis caching layer, the version management, the client SDK for parsing and rendering, the fallback logic, the section isolation — all custom code that needs ongoing maintenance.

Pyramid gives you the same architectural pattern as a managed platform:

Capability JobKorea (DIY) Pyramid
Screen/Section model Custom implementation Built-in with visual editor
Targeting & segmentation Custom dimensions Flexible targeting rules
A/B testing testType field Built-in experimentation
Version-based caching Custom Redis + client logic Managed CDN + SDK caching
Section isolation Custom error boundaries Automatic per-component isolation
Fallback logic Custom fallback config Declarative fallback chains
Native rendering Custom native components BYOC — your Compose & SwiftUI components
Admin tool Built internally Dashboard included
Setup time Months Days

Ship UI changes without app releases — like JobKorea, without the DIY

Pyramid gives you Screen/Section architecture, version-based caching, native rendering, and built-in experimentation. All managed.

Get Early Access →

Frequently Asked Questions

What SDUI architecture does JobKorea use?

JobKorea uses a two-tier Screen/Section architecture. Screens are top-level page containers parameterized by siteType (JOBKOREA/ALBAMON), deviceType, screenType, segmentType, and testType. Sections are reusable UI blocks within screens, each with its own renderType and independent API data source.

How does JobKorea handle caching in their SDUI system?

They use version-based caching with timestamp strings like "20260219123045". The client checks the version (~100 bytes) on each screen load and only downloads the full screen configuration when the version changes. This keeps network overhead near zero for the vast majority of screen loads.

How does JobKorea do A/B testing with server-driven UI?

A/B testing is built directly into the Screen definition via a testType field. Different test groups receive entirely different Screen configurations — including different sections, different ordering, and different layouts — without any client-side code changes.

What happens if a section fails to load in JobKorea's SDUI?

Each section loads data independently from its own API endpoint. If one section fails, the rest of the screen renders normally. Sections can also define explicit fallbacks — for example, falling back from a personalized carousel to a simple popular jobs list.

Related Articles

Source: JobKorea Engineering Blog — Server-Driven UI adoption case study, February 2026