# How to Migrate from WebView-Based Apps to Native-Rendered Components in Native SDK

> Migrate WebView apps to native components with Native SDK. Remove webview capability, update UiApp, implement declarative markup, and move logic to compiled cores. Boost performance now.

- Repository: [Vercel Labs/native](https://github.com/vercel-labs/native)
- Tags: migration-guide
- Published: 2026-07-18

---

**To migrate from WebView-based apps to native-rendered components in Native SDK, remove the `webview` capability from your project manifest, delete `Options.web_panes` from your `UiApp` constructor, reimplement your interface with declarative `.native` markup, and move business logic into compiled Zig or TypeScript cores.**

The `vercel-labs/native` repository provides a Native SDK that builds desktop applications rendering UI directly with the engine rather than embedding a browser. A WebView-based app typically declares the **webview** capability, configures `Options.web_panes`, and drives navigation through `native-sdk.webview.*` bridge calls. The steps below convert that hybrid architecture into a pure-native application with a smaller binary, faster startup, and tighter OS integration.

## Remove the `webview` Capability from `app.zon`

Every WebView-based app lists the webview capability in its project manifest so the build system links the WebView runtime. Open `app.zon` and delete `"webview"` from the `capabilities` array.

```json
{
  "name": "my_app",
  "version": "0.1.0",
  // "capabilities": [ "webview", "js_bridge" ],   // <-- delete or comment out
  "frontend": null
}

```

After this change, the build graph no longer includes WebView2 or CEF SDKs. According to [`skill-data/core/references/bridge-security-native-capabilities.md`](https://github.com/vercel-labs/native/blob/main/skill-data/core/references/bridge-security-native-capabilities.md) (lines 10–12), removing this entry also prevents the stub `WebViewLayerNotBuilt` error on platforms that do not provide a web layer.

## Delete `Options.web_panes` from `src/main.zig`

The webview widget attaches to the canvas layout through the `.web_panes` field inside the `UiApp` constructor. In `src/main.zig`, remove that field and any helper that fills `App.WebViewPane`.

```zig
const App = native_sdk.UiApp(Model, Msg);
pub fn main(init: std.process.Init) !void {
    const app_state = try App.create(std.heap.page_allocator, .{
        .name = "my_app",
        .scene = shell_scene,          // one window, one gpu_surface view
        .canvas_label = "my-app-canvas",
        .update = update,
        .markup = .{
            .source = @embedFile("app.native"),
            .watch_path = "src/app.native",
            .io = init.io,
        },
        // .web_panes = panes,   // <-- REMOVE THIS LINE
    });
    defer app_state.destroy();
    app_state.model = initialModel();
    try runner.runWithOptions(app_state.app(), .{}, init);
}

```

As documented in [`skill-data/native-ui/SKILL.md`](https://github.com/vercel-labs/native/blob/main/skill-data/native-ui/SKILL.md) (lines 63–78), the original snippet anchors a webview pane to the layout; omitting that entry detaches the browser widget entirely.

## Replace WebView UI with Native Markup

Create a `.native` view—commonly at `src/app.native`—and declare **native-rendered components** using the built-in widget catalog. Elements such as `row`, `column`, `list`, `button`, and `text` are written declaratively, and dynamic data binds from the model via `{bindings}`.

```html
<column gap="12" grow="1">
  <text size="heading">Welcome to MyApp</text>
  <list grow="1" items="{model.items}" key="{item.id}"
        label="{item.title}" on-press="select:{item.id}" />
  <button variant="primary" on-press="add_item">Add Item</button>
</column>

```

No external HTML page or JavaScript bridge is required. The markup compiles to the same widget tree that a hand-written `canvas.Ui` builder would produce, as described in the native markup overview in [`skill-data/native-ui/SKILL.md`](https://github.com/vercel-labs/native/blob/main/skill-data/native-ui/SKILL.md) (lines 6–13).

## Move Business Logic into Zig or TypeScript Cores

Code that previously ran inside the web page—fetching data, managing UI state, and handling events—must move into the app’s **core**. Implement the core in Zig for maximum performance or TypeScript for rapid iteration. The pattern centers on three pieces: a `Model`, a `Msg` union, and an `update` function.

The engine then drives the UI directly from the model without a runtime JavaScript interpreter. You can see the expected core shape in the repository’s [`README.md`](https://github.com/vercel-labs/native/blob/main/README.md) (lines 58–66).

## Remove WebView-Specific Bridge Calls

Delete all remaining calls to `native-sdk.webview.create`, `native-sdk.webview.navigate`, `native-sdk.webview.setZoom`, and related bridge APIs. These functions are documented in [`skill-data/core/references/bridge-security-native-capabilities.md`](https://github.com/vercel-labs/native/blob/main/skill-data/core/references/bridge-security-native-capabilities.md) (lines 105–111).

Once the `webview` capability is removed, the symbols are no longer exported. The compiler surfaces any leftover bridge calls as build errors, which makes cleanup deterministic.

## Update Build and Test Workflows

Validate the migration by running the native toolchain against your project.

1. Run `native dev` to confirm the app launches in a native window.
2. Run `native test` or `zig build test-example-<name>` to execute snapshots and UI-automation tests.

Because the deterministic reference renderer now draws only native widgets, golden images become significantly smaller and the test suite reflects pure-native rendering behavior.

## Handle Native Assets (Optional)

If your WebView previously loaded images, fonts, or other resources via web URLs, embed them as native assets instead. Use `@embedFile` to include files at compile time, or register custom images with `canvas.icons.registerAppIcons`. Built-in widgets such as `<icon>` and `<avatar>` already support embedded icons. The “Images” section of the native markup guide covers asset registration in detail.

## Summary

- **Delete `"webview"`** from the `capabilities` array in `app.zon` to strip the WebView runtime from the build.
- **Remove `.web_panes`** and any `App.WebViewPane` helpers from `src/main.zig` to detach the browser widget.
- **Adopt `.native` markup** with `row`, `column`, `list`, and `button` to render native components declaratively.
- **Migrate logic** into a compiled Zig or TypeScript core using `Model`, `Msg`, and `update`.
- **Strip bridge calls** to `native-sdk.webview.*` APIs; the compiler will flag any survivors.
- **Validate** with `native dev` and `native test` to lock in the pure-native pipeline.

## Frequently Asked Questions

### Do I need to rewrite my entire app in Zig to migrate away from WebView?

No. You can write the core in **Zig** or **TypeScript** depending on your team’s preference. The Native SDK compiles either language at build time, and both integrate with the same declarative `.native` markup and `UiApp` lifecycle.

### What happens if I forget to remove `native-sdk.webview.create` from my code?

The compiler emits a build error. Once you delete the `"webview"` capability from `app.zon`, the bridge symbols documented in [`skill-data/core/references/bridge-security-native-capabilities.md`](https://github.com/vercel-labs/native/blob/main/skill-data/core/references/bridge-security-native-capabilities.md) are no longer exported, so leftover calls fail at compile time rather than runtime.

### Can I mix native-rendered components and WebView panes in the same app?

Yes, but only while the `"webview"` capability remains active. The [`examples/canvas-preview/README.md`](https://github.com/vercel-labs/native/blob/main/examples/canvas-preview/README.md) demonstrates a mixed native-canvas and live-webview layout. To achieve a fully native application, you must eventually remove the capability and all `Options.web_panes` entries.

### How do I load images without a WebView HTML layer?

Use `@embedFile` to bake assets into the binary at compile time, or call `canvas.icons.registerAppIcons` to register custom images with the canvas system. Built-in widgets like `<icon>` and `<avatar>` consume these embedded assets directly without any web server.