This content originally appeared on DEV Community and was authored by Alex Aslam
“We launched our mobile app in 6 weeks—without writing a single line of Swift or Kotlin.”
When our CEO demanded a mobile app, our team groaned. None of us knew React Native, and the thought of maintaining two codebases (iOS + Android) made us shudder.
Then we discovered Turbo Native—the secret weapon that let us ship a fully native-feeling app using our existing Rails backend and zero custom API endpoints.
Here’s how it works, when to use it, and the brutal tradeoffs we learned the hard way.
1. What is Turbo Native?
The TL;DR
Turbo Native lets you:
- Wrap your Rails app in a native shell (iOS/Android)
- Reuse 100% of your HTML views
- Add native navigation/gestures with minimal glue code
It’s not a cross-platform framework. Instead:
-
iOS: Uses
WKWebView
+ native navigation (Swift/ObjC) -
Android: Uses
WebView
+ native nav (Kotlin/Java)
2. How We Built It in 6 Weeks
Step 1: The Native Shell
We started with the official Turbo iOS and Turbo Android templates:
// iOS (Swift)
let turboSession = WKWebViewConfiguration()
let turboNavController = TurboNavigationController()
turboNavController.route("/") // 👈 Loads your Rails root_url
// Android (Kotlin)
val turboSession = TurboSession()
turboSession.visit("https://yourapp.com")
Key Insight: This is just a browser in native clothing—but with critical extras:
- Native tab bars
- Swipe-back gestures
- Offline support
Step 2: Progressive Enhancement
We marked up our existing Rails views to trigger native behavior:
<!-- Show native back button -->
<meta name="turbo-native-navigation" content="back">
<!-- Use native tabs -->
<div data-turbo-native-tab="home">Home</div>
<div data-turbo-native-tab="profile">Profile</div>
<!-- Native pull-to-refresh -->
<turbo-frame id="feed" data-native-refresh="true">
Step 3: The Bridge
For device features (camera, GPS), we used Stimulus controllers + native callbacks:
// Rails: Stimulus controller
export default class extends Controller {
takePhoto() {
window.TurboNativeBridge.postMessage("requestCamera")
}
}
// iOS: Handle the message
func webView(_ webView: WKWebView, didReceive message: WKScriptMessage) {
if message.name == "requestCamera" {
presentCamera()
}
}
3. The Good, Bad, and Ugly
Why We Loved It
- Shipped 6x faster than React Native
- Zero API changes (used existing HTML)
- App Store approval in 24 hours (no JS engine = fewer red flags)
Where It Hurt
- No offline-first (without serious work)
- Animations were clunky (compared to pure native)
- Debugging = Safari Web Inspector (no React DevTools)
The Biggest Surprise
Our App Store rating was higher than our competitor’s React Native app. Users couldn’t tell it was a web view because:
- We used native navigation stacks
- Added skeleton loading states
- Avoided janky transitions
4. Turbo Native vs. Alternatives
Turbo Native | React Native | Flutter | PWA | |
---|---|---|---|---|
Code Reuse | 90% | 50% | 100% | 100% |
Native Feel | 85% | 95% | 90% | 50% |
Team Skills Required | Rails + CSS | JavaScript | Dart | Rails |
Offline Support | Limited | Good | Great | Great |
Best for:
- Internal tools
- CRUD-heavy apps
- Teams with strong Rails skills
Avoid for:
- Games
- Video-heavy apps
- Apps needing deep device integration
5. When to Go Hybrid
We mixed native screens for key flows:
// Swift: Show checkout natively
if path == "/checkout" {
presentNativeCheckout()
} else {
visit(path) // Default to Turbo
}
Hybrid wins:
- Payment flows (better Apple Pay/Google Pay support)
- Camera/upload screens
- Complex animations
6. How to Get Started
1. Try the Demos
2. Audit Your Rails App
- Fix responsive design issues
- Add turbo-native meta tags
- Test with Safari’s “Request Mobile Site”
3. Plan Your Native Extras
- List required device APIs (camera, GPS)
- Design native fallbacks for slow networks
“But What About Performance?”
Our benchmarks:
- Cold start: 1.2s (vs. 0.8s native)
- Navigation: 0.3s (vs. 0.1s native)
- Memory usage: 30% higher than native
For 95% of apps, this doesn’t matter.
Tried Turbo Native? Share your wins/fails below!
This content originally appeared on DEV Community and was authored by Alex Aslam