How to Migrate from WebView-Based Apps to Native-Rendered Components in Native SDK
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.
{
"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 (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.
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 (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}.
<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 (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 (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 (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.
- Run
native devto confirm the app launches in a native window. - Run
native testorzig 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 thecapabilitiesarray inapp.zonto strip the WebView runtime from the build. - Remove
.web_panesand anyApp.WebViewPanehelpers fromsrc/main.zigto detach the browser widget. - Adopt
.nativemarkup withrow,column,list, andbuttonto render native components declaratively. - Migrate logic into a compiled Zig or TypeScript core using
Model,Msg, andupdate. - Strip bridge calls to
native-sdk.webview.*APIs; the compiler will flag any survivors. - Validate with
native devandnative testto 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →