Bringing Goodnotes to the Web with Swift and WebAssembly

frizlab1 pts0 comments

Bringing Goodnotes to the web with Swift and WebAssembly | Swift.org

Bringing Goodnotes to the web with Swift and WebAssembly

Yuta Saito

Yuta Saito is a Software Engineer at Goodnotes, working on WebAssembly platform support in Swift.

June 1, 2026

Goodnotes has been helping millions of users take handwritten notes on iPad for over a decade, earning recognition as Apple’s iPad App of the Year in 2022. Today, the same Swift code that powers our iOS app also runs seamlessly in web browsers through WebAssembly, delivering the exact same ink rendering and note-taking experience users love.

This journey demonstrates that Swift excels as a cross-platform language, running high-performance applications while sharing the same codebase. Every bug fix and improvement to Goodnotes benefits all our users simultaneously, regardless of which platform they use.

After two years of development and over two years in production at Goodnotes, we’ve shown that Swift on WebAssembly is a viable, powerful approach for building complex, performance-critical web applications.

Why we chose Swift for the web

When we decided to bring Goodnotes to the web in 2021, we faced a critical decision. After more than 10 years of development, we had accumulated millions of lines of Swift code that implemented countless refinements and optimizations for digital ink rendering, document synchronization, conflict resolution using Conflict-Free Replicated Data Types (CRDTs), and content search and document indexing.

We need to maintain more than 60 Frames Per Second (FPS) for real-time ink rendering, which makes performance critical. A JavaScript rewrite, Flutter, or Kotlin Multiplatform would all require rewriting our entire rendering engine from scratch, a substantial undertaking that would have delayed our web launch by years and inevitably introduced behavioral differences between platforms.

SwiftWasm emerged as the solution. This community-driven project allows Swift code to compile to WebAssembly, running in browsers with good performance. We started experimenting with SwiftWasm, building prototypes to validate the approach. Our first experiment focused on our handwriting component, a performance-critical part of Goodnotes that would serve as a good indicator of WebAssembly’s capabilities. The results were promising enough that we committed to this path.

The most compelling benefit wasn’t just code reuse, but the guarantee of behavioral consistency. When users draw a stroke on their iPad and later open the same document on the web, they see exactly the same curves, the same pressure sensitivity, the same ink flow. This isn’t because we carefully reimplemented the same algorithms twice: it’s because it’s literally the same Swift code running on both platforms.

Technical architecture

Goodnotes Architecture: Shared Swift code between iOS and Web platforms.

Our architecture is built around a clear separation between platform-specific UI components and shared business logic. This design enables us to maintain behavioral consistency while leveraging platform-native capabilities where appropriate.

Shared core components

The heart of our application consists of three main parts:

Content Rendering Engine : This handles the real-time rendering of notebook content and interactive ink strokes. We use a custom rendering engine built on low-level graphics APIs: Metal on iOS and WebGL on the web. The rendering logic is almost entirely shared, with only platform abstraction layers implemented separately for each platform.

Business Logic Layer : Document modeling, handwriting recognition, and document indexing are all implemented in shared Swift packages.

View Models : Core view models that handle tool interactions and user gestures are shared across platforms.

Code sharing metrics

Our codebase demonstrates significant code reuse:

Total Web Swift codebase : 2.2 million lines of code

Shared Swift code : 1.47 million lines (66% of the web app, 34% of the iOS app)

While lines of code isn’t the best metric, these numbers reflect the substantial business logic and rendering engine that we successfully share between platforms.

Binary size and loading

The final WebAssembly binary is approximately 50 MB, which compresses to 12 MB with Brotli compression. We use Service Workers for efficient caching and fast load times for users.

JavaScript interoperability

We use JavaScriptKit for seamless interoperability between Swift and JavaScript. This allows us to integrate with the existing web ecosystem while keeping our core logic in Swift.

Platform compatibility considerations

When sharing code between iOS and WebAssembly targets, we encountered several important considerations:

Concurrency Model : libdispatch APIs are unavailable on WebAssembly targets. We migrated from direct libdispatch usage to Swift Concurrency’s async/await and actors, for better cross-platform compatibility.

Architecture Differences : On wasm32, Swift’s Int has a...

swift code webassembly goodnotes platform rendering

Related Articles