🚀 Complete Guide: Converting Next.js to PWA & TWA
Transform your Next.js website into a Progressive Web App and wrap it as an Android application using Trusted Web Activity. Every step explained in detail!
📋 Table of Contents
- What is a PWA?
- What is a TWA?
- Prerequisites & Setup
- Step 1: Convert Next.js to PWA
- Install Dependencies
- Configure next.config.js
- Create Web App Manifest
- Prepare App Icons
- Update Document Head
- Build and Test PWA
- Step 2: Wrap PWA as TWA
- Create Asset Links File
- Install Bubblewrap CLI
- Initialize TWA Project
- Generate Keystore
- Extract SHA-256 Fingerprint
- Update Asset Links
- Build and Install APK
- Publish to Play Store
- Troubleshooting
- Pros & Cons
- Conclusion
🌐 What is a Progressive Web App (PWA)?
A Progressive Web App is a web application that uses modern web capabilities to deliver an app-like experience to users. PWAs are:
- Reliable - Load instantly and work offline
- Fast - Respond quickly to user interactions
- Engaging - Feel like a natural app on the device
- Installable - Can be installed on home screen without app store
📱 What is a Trusted Web Activity (TWA)?
TWA is a new way to integrate your web-app content such as your PWA with your Android app using Chrome Custom Tabs. It allows you to:
- Run your PWA in fullscreen without browser UI
- Distribute through Google Play Store
- Maintain the same URL and share the same storage
- Provide native Android app experience
⚙️ Prerequisites & Setup
Before You Start, Make Sure You Have:
- Node.js (version 14 or higher) installed
- A working Next.js application
- Your website deployed and accessible online
- Android Studio or Android SDK installed (for TWA)
- Java Development Kit (JDK) installed
- A domain where you can upload files (for asset links)
🔧 Step 1: Convert Next.js to PWA
1.1 Install Required Dependencies
Navigate to your Next.js project root directory and install the necessary packages:
npm install next-pwa workbox-webpack-plugin --save
What these packages do:
next-pwa
: Next.js plugin that adds PWA functionalityworkbox-webpack-plugin
: Google's library for adding offline support
1.2 Configure next.config.js
Create or update the next.config.js
file in your project root:
├── pages/
├── public/
├── styles/
├── next.config.js ← Create/Update this file
└── package.json
Add the following configuration:
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development',
register: true,
skipWaiting: true,
runtimeCaching: [
{
urlPattern: /^https?.*/,
handler: 'NetworkFirst',
options: {
cacheName: 'offlineCache',
expiration: {
maxEntries: 200,
},
},
},
],
});
module.exports = withPWA({
// Your other Next.js config options here
reactStrictMode: true,
swcMinify: true,
});
1.3 Create Web App Manifest
Create the manifest file at public/manifest.json
:
├── favicon.ico
├── manifest.json ← Create this file
└── icons/ ← Create this folder (next step)
Create the manifest.json file with the following content:
{
"name": "My Amazing Next.js PWA",
"short_name": "NextPWA",
"description": "A Progressive Web App built with Next.js",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"orientation": "portrait-primary",
"categories": ["productivity", "utilities"],
"lang": "en",
"dir": "ltr",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
- name: Full name of your app (shown when installing)
- short_name: Short name (shown on home screen)
- theme_color: Color of the browser UI
- background_color: Background color while app loads
- display: How the app appears (standalone = no browser UI)
1.4 Prepare App Icons
Create the icons folder in your public directory:
You need to create PNG icons in the following sizes:
- 72x72 pixels
- 96x96 pixels
- 128x128 pixels
- 144x144 pixels
- 152x152 pixels
- 192x192 pixels
- 384x384 pixels
- 512x512 pixels
- Create a high-resolution square image (1024x1024 or higher)
- Use online tools like PWA Builder Image Generator
- Or use image editing software to resize manually
- Save each size as
icon-WIDTHxHEIGHT.png
Your final folder structure should look like:
└── icons/
├── icon-72x72.png
├── icon-96x96.png
├── icon-128x128.png
├── icon-144x144.png
├── icon-152x152.png
├── icon-192x192.png
├── icon-384x384.png
└── icon-512x512.png
1.5 Update Document Head
Create or update pages/_document.js
to include PWA meta tags:
├── _app.js
├── _document.js ← Create/Update this file
├── index.js
└── ...
Add the following code to _document.js:
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA Meta Tags */}
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#000000" />
<meta name="application-name" content="NextPWA" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="NextPWA" />
<meta name="description" content="A Progressive Web App built with Next.js" />
<meta name="format-detection" content="telephone=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="msapplication-TileColor" content="#000000" />
{/* Apple Touch Icons */}
<link rel="apple-touch-icon" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
{/* Favicon */}
<link rel="shortcut icon" href="/favicon.ico" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
1.6 Build and Test PWA
Build your Next.js application:
Start the production server:
Test your PWA:
- Open your browser and navigate to
http://localhost:3000
- Open Chrome DevTools (F12)
- Go to the Application tab
- Check Manifest section - you should see your app details
- Check Service Workers section - should show registered worker
- Look for the install prompt in the address bar
- Install button appears in browser address bar
- Lighthouse PWA audit shows green checkmarks
- App can be installed to desktop/mobile home screen
- Service worker is registered and active
📦 Step 2: Wrap PWA as TWA (Trusted Web Activity)
2.1 Create Asset Links File
Create the .well-known directory in your public folder:
Create public/.well-known/assetlinks.json
:
├── .well-known/
│ └── assetlinks.json ← Create this file
├── icons/
└── manifest.json
Add the following template to assetlinks.json:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp.twa",
"sha256_cert_fingerprints": [
"YOUR_SHA256_FINGERPRINT_WILL_GO_HERE"
]
}
}
]
Deploy your website with the updated assetlinks.json file and verify it's accessible at:
2.2 Install Bubblewrap CLI
Install Bubblewrap globally:
Verify installation:
Bubblewrap is Google's official tool for building Android apps that launch Progressive Web Apps using Trusted Web Activity.
2.3 Initialize TWA Project
Create a new directory for your TWA project:
cd my-app-twa
Initialize the TWA project:
You'll be prompted with several questions. Here's how to answer them:
- packageId: Use reverse domain notation like
com.yourcompany.yourapp.twa
- appName: The name that appears in Android app list
- host: Your domain without https:// (e.g.,
yourdomain.com
) - keystore: Choose "Generate" for new projects
- Play Store: Say "yes" if you plan to publish
Example answers:
? Package ID: com.mycompany.myapp.twa
? App name: My PWA App
? Host: myapp.com
? Start URL: /
? Icon URL: https://myapp.com/icons/icon-512x512.png
? Theme color: #000000
? Background color: #ffffff
? Generate keystore: Yes
? Keystore password: [enter secure password]
? Key alias: my-app-key
? Key password: [enter secure password]
? Include Play Store metadata: Yes
2.4 Generate Keystore (Automatic)
If you chose "Generate" in the previous step, Bubblewrap automatically creates a keystore file. You'll see something like:
├── android/
├── store_assets/
├── twa-manifest.json
└── android-keystore.keystore ← Generated keystore
- NEVER commit your keystore to version control
- BACKUP your keystore file securely
- REMEMBER your passwords - you can't recover them
- If you lose your keystore, you can't update your published app
2.5 Extract SHA-256 Fingerprint
Extract the SHA-256 fingerprint from your keystore:
Enter your keystore password when prompted. You'll see output like this:
Certificate fingerprints:
SHA1: AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD
SHA256: 11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00
Copy the SHA256 line (the long string after "SHA256:"):
11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00
- Windows:
C:\Program Files\Java\jdk-XX\bin\keytool.exe
- macOS:
/usr/bin/keytool
- Linux:
/usr/bin/keytool
2.6 Update Asset Links with SHA-256
Update your public/.well-known/assetlinks.json
with the real SHA-256 fingerprint:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.mycompany.myapp.twa",
"sha256_cert_fingerprints": [
"11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00"
]
}
}
]
Deploy the updated assetlinks.json to your website.
Verify the asset links are working:
You can also test your asset links using Google's official tool:
Digital Asset Links Tool
2.7 Build and Install APK
Build the APK from your TWA directory:
bubblewrap build
You should see output like:
Building the Android App...
Build successful!
Generated Android App at: app-release-unsigned.apk
- Enable Developer Options on your Android device
- Enable USB Debugging
- Connect your device via USB
- Accept the debugging prompt on your phone
Install the APK to your connected device:
Or install manually using ADB:
- Install Android SDK Platform Tools
- Add the platform-tools directory to your PATH
- Or use the full path to adb
2.8 Test Your TWA App
Launch the app on your Android device and verify:
- ✅ App opens without browser UI (no address bar)
- ✅ App icon appears in app drawer
- ✅ App behaves like a native Android app
- ✅ Back button works correctly
- ✅ App can be found in Android settings
- If app opens in browser, check your asset links
- Use Chrome DevTools for debugging web content
- Check Android logcat for TWA-specific errors
2.9 Prepare for Play Store Publishing
Build an Android App Bundle (AAB) for Play Store:
This creates: app-release.aab
- Use AAB format (not APK) for new apps
- Your PWA must meet quality guidelines
- Asset links must be properly configured
- App must add value beyond just wrapping a website
Play Store Upload Process:
- Go to Google Play Console
- Create a new app
- Use the same package name from your TWA
- Upload the
app-release.aab
file - Fill in app details, screenshots, descriptions
- Submit for review
Bubblewrap generates store assets in the
store_assets/
folder including icons and screenshots. Use these for your Play Store listing.
🔧 Troubleshooting Common Issues
PWA Issues
Service Worker Not Registering:
- Check if you're testing in production mode (
npm run build && npm run start
) - Verify
next.config.js
configuration - Check browser console for errors
- Clear browser cache and try again
Install Prompt Not Showing:
- Ensure all PWA criteria are met (HTTPS, service worker, manifest)
- Check Lighthouse PWA audit
- Some browsers require user engagement before showing prompt
- Verify manifest.json is properly linked
Icons Not Displaying:
- Check icon file paths in manifest.json
- Ensure icon files exist and are accessible
- Verify icon dimensions match manifest specifications
- Use proper image formats (PNG recommended)
TWA Issues
App Opens in Browser Instead of Fullscreen:
- Verify asset links are properly configured and accessible
- Check SHA-256 fingerprint matches your keystore
- Ensure package name matches between TWA and asset links
- Wait up to 24 hours for asset link verification
Build Failures:
- Ensure Java SDK is properly installed
- Check Android SDK installation
- Verify keystore file exists and passwords are correct
- Update Bubblewrap to latest version
Installation Issues:
- Enable "Install from Unknown Sources" on Android
- Check if ADB is properly installed and in PATH
- Verify USB debugging is enabled
- Try manual installation:
adb install app-release-unsigned.apk
⚖️ Pros & Cons Analysis
Progressive Web App (PWA)
✅ Advantages
- Cross-platform: Works on any modern browser
- No app store approval: Deploy instantly
- Automatic updates: Update via web deployment
- Offline functionality: Works without internet
- Lower development cost: Single codebase
- SEO benefits: Still discoverable by search engines
- No installation barriers: Users can try immediately
❌ Disadvantages
- Limited native APIs: Can't access all device features
- Browser dependency: Performance tied to browser
- iOS limitations: Limited PWA support on iOS
- Discovery challenges: Not in app stores by default
- User education: Users may not know they can install
- Battery usage: May use more battery than native apps
Trusted Web Activity (TWA)
✅ Advantages
- Native app experience: Full-screen, no browser UI
- App store distribution: Available on Google Play
- Better discoverability: Found in app stores
- Native integrations: Push notifications, shortcuts
- User familiarity: Users understand app stores
- Shared storage: Same data as web version
- Automatic updates: Web content updates automatically
❌ Disadvantages
- Android only: Limited to Android platform
- Complex setup: Requires keystore and asset links
- App store approval: Subject to review process
- Maintenance overhead: Two deployment processes
- Chrome dependency: Requires Chrome on device
- Asset link complexity: Can be tricky to configure
🚀 Advanced Features & Optimizations
Enhanced PWA Features
Add Push Notifications:
// In your service worker or main app
if ('Notification' in window && 'serviceWorker' in navigator) {
// Request permission
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
// Set up push notifications
navigator.serviceWorker.ready.then(registration => {
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'YOUR_VAPID_KEY'
});
});
}
});
}
Add Background Sync:
// Register background sync in service worker
self.addEventListener('sync', event => {
if (event.tag === 'background-sync') {
event.waitUntil(doSomeBackgroundWork());
}
});
// Request sync in main app
navigator.serviceWorker.ready.then(registration => {
return registration.sync.register('background-sync');
});
TWA Customizations
Custom Splash Screen:
Edit twa-manifest.json
in your TWA project:
{
"packageId": "com.yourcompany.yourapp.twa",
"host": "yourapp.com",
"name": "Your App Name",
"launcherName": "YourApp",
"display": "standalone",
"themeColor": "#000000",
"backgroundColor": "#ffffff",
"startUrl": "/",
"iconUrl": "https://yourapp.com/icons/icon-512x512.png",
"splashScreenFadeOutDuration": 300,
"enableNotifications": true,
"fallbackType": "customtabs"
}
Add Custom Intent Filters:
You can modify the generated Android manifest to handle custom URL schemes or file types.
⚡ Performance Optimization
Optimize Service Worker Caching:
// In your custom service worker
import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies';
// Clean up old caches
cleanupOutdatedCaches();
// Precache and route
precacheAndRoute(self.__WB_MANIFEST);
// Cache images with CacheFirst strategy
registerRoute(
({request}) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [{
cacheKeyWillBeUsed: async ({request}) => `${request.url}?version=1`,
}],
}),
);
// Cache API calls with StaleWhileRevalidate
registerRoute(
({url}) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
cacheName: 'api-cache',
}),
);
Optimize Bundle Size:
Update your next.config.js
:
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development',
register: true,
skipWaiting: true,
});
module.exports = withPWA({
// Enable compression
compress: true,
// Optimize images
images: {
optimization: {
include: ['jpg', 'jpeg', 'png', 'webp'],
},
},
// Enable experimental features
experimental: {
optimizeCss: true,
},
// Webpack optimizations
webpack: (config, { isServer }) => {
if (!isServer) {
config.optimization.splitChunks.chunks = 'all';
}
return config;
},
});
✅ Testing Checklist
PWA Testing Checklist:
- □ Lighthouse PWA audit passes (score > 90)
- □ Install prompt appears in supported browsers
- □ App works offline
- □ Service worker registers and updates properly
- □ Manifest is valid and accessible
- □ Icons display correctly on all sizes
- □ App is responsive on all devices
- □ HTTPS is properly configured
- □ Meta tags are properly set
- □ Performance metrics are acceptable
TWA Testing Checklist:
- □ App opens in fullscreen (no browser UI)
- □ Asset links are properly configured
- □ SHA-256 fingerprint matches keystore
- □ Package name is consistent
- □ App icon appears in Android launcher
- □ Back button navigation works
- □ App appears in Android app settings
- □ Deep links work properly
- □ Splash screen displays correctly
- □ App handles network changes gracefully
🔒 Security Considerations
PWA Security:
- HTTPS Only: Always serve your PWA over HTTPS
- Content Security Policy: Implement strict CSP headers
- Service Worker Security: Validate all cached content
- Data Validation: Sanitize all user inputs
- Secure Storage: Use IndexedDB for sensitive data
TWA Security:
- Keystore Protection: Store keystore securely, never in version control
- Asset Links: Regularly verify asset links are working
- Certificate Pinning: Consider implementing certificate pinning
- App Signing: Use Google Play App Signing for production
- Intent Filters: Carefully configure intent filters
🔧 Ongoing Maintenance
PWA Maintenance Tasks:
- Regular Updates: Keep dependencies updated
- Performance Monitoring: Use tools like Lighthouse CI
- Cache Management: Regularly review and update caching strategies
- Browser Compatibility: Test on new browser versions
- Manifest Updates: Update app information as needed
TWA Maintenance Tasks:
- Keystore Backup: Regularly backup keystore files
- Asset Links Monitoring: Monitor asset links availability
- Play Store Updates: Update app metadata and screenshots
- Android Compatibility: Test on new Android versions
- Bubblewrap Updates: Keep Bubblewrap CLI updated
🎉 Conclusion
Congratulations! You've successfully learned how to convert a Next.js application into a Progressive Web App and wrap it as a Trusted Web Activity for Android. This powerful combination gives you:
- 🌐 Cross-platform reach with your PWA
- 📱 Native app experience on Android with TWA
- 🚀 Easy deployment and updates through web deployment
- 📈 Better discoverability through app stores
- ⚡ Improved performance with caching and offline support
🔗 Helpful Resources:
🚨 Remember:
- Always test thoroughly on real devices
- Keep your keystore files secure and backed up
- Monitor your PWA performance regularly
- Stay updated with PWA and TWA best practices
- Consider user feedback for continuous improvement
Happy coding! 🚀
Your Next.js app is now ready for the modern web and mobile app stores!
Comments
Post a Comment