Use Case: App Onboarding

App Onboarding with Deep Links

Create personalized first-run experiences with deferred deep links. Guide new users directly to the most relevant content for them on their first app launch.

The Opportunity

The personalization opportunity

First impressions matter. When users install your app, you have a critical moment to show them what's most relevant to them. Without deferred deep links, that moment is lost.

Why personalized onboarding matters

  • First-time user activation: Users are most engaged in the first 5 minutes. Show them relevant content immediately.
  • Context from the web: User came from a product page, category, or specific content. Preserve that context in the app.
  • Skip irrelevant steps: Don't force everyone through generic onboarding. Route users based on their entry point.
  • Higher conversion: Users who see relevant content convert at higher rates than those shown a generic home screen.

Generic onboarding

  1. 1.User clicks link to see specific product
  2. 2.App installs (if needed)
  3. 3.Generic welcome screen appears
  4. 4.User must search for the product they wanted
  5. 5.Friction. User may abandon.

Personalized with deep links

  1. 1.User clicks link to see specific product
  2. 2.App installs (if needed)
  3. 3.App opens directly to that product
  4. 4.No friction. User sees exactly what they wanted.
  5. 5.Higher likelihood of purchase or engagement.
How It Works

How deferred deep links enable personalization

Step 1: User on web or email

A user views a specific product on your website or in a marketing email. You create a deep link to that product: https://myapp.redirectly.app/product/blue-shoes-size-10

Step 2: User taps link on mobile

User taps the link on their phone. If the app is installed, it opens directly to the product screen.

If the app is not installed, Redirectly captures the full URL including product ID and stores it.

Step 3: Install and redirect

User is sent to App Store or Play Store. The product context is preserved in Redirectly's servers.

Step 4: App retrieves deferred link on launch

When the app launches for the first time, the Redirectly SDK queries: "What page should this user see?" Redirectly responds: "/product/blue-shoes-size-10".

Step 5: App navigates to product

Your app receives the deep link and navigates directly to the product page. User sees exactly what they wanted. First impression is perfect.

Real-World Scenarios

Onboarding scenarios powered by deferred deep links

E-commerce: Product page

User sees a shoe on social media. Clicks link. Doesn't have your shopping app. App installs and opens directly to that shoe product, size and color included.

Result: One-tap purchase. No searching required.

Streaming: Watch specific video

Friend shares a YouTube video link via your streaming app's sharing feature. Recipient doesn't have the app yet. App installs and opens directly to that video.

Result: Instant video playback. No discovery friction.

Social: User profile

Someone shares their profile link. New user clicks it. App installs and opens directly to that person's profile, already ready to follow or message.

Result: Immediate connection. Lower friction to social engagement.

Marketplace: Category or search results

Marketing campaign sends users to a specific category (e.g., "Winter Jackets"). Users without the app get it installed and see that category first.

Result: Campaign relevance maintained. Higher campaign ROI.

Implementation

Implementation guide

Step 1: Generate contextual deep links

Whenever a user could install your app, create a deep link with context:

# Web backend - Generate deep link with context def generate_deep_link(product_id, user_id=None, category=None): params = {'product': product_id} if category: params['category'] = category if user_id: params['user'] = user_id deep_link = build_redirectly_url(params) return deep_link # Usage: On product pages, in emails, in social shares product_link = generate_deep_link( product_id='blue_shoes_10', category='footwear' ) # Result: https://myapp.redirectly.app/shop?product=blue_shoes_10&category=footwear

Step 2: Initialize and listen for deferred link

In your Flutter app, initialize Redirectly and listen for the deferred link on first launch:

import 'package:redirectly/redirectly.dart'; import 'package:go_router/go_router.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize Redirectly await Redirectly.initialize( apiKey: 'your_api_key_here', ); // Get deferred link on first launch final initialLink = await Redirectly.getInitialLink(); // Determine initial route based on deep link String initialRoute = '/home'; if (initialLink != null) { initialRoute = _routeFromDeepLink(initialLink); } runApp(MyApp(initialRoute: initialRoute)); } String _routeFromDeepLink(RedirectlyLink link) { switch (link.path) { case 'product': final productId = link.queryParameters['product']; return '/product/$productId'; case 'category': final categoryId = link.queryParameters['category']; return '/category/$categoryId'; case 'profile': final userId = link.queryParameters['user']; return '/profile/$userId'; default: return '/home'; } }

Step 3: Route to correct screen on startup

Use go_router to navigate to the personalized screen on app launch:

final GoRouter _buildRouter(String initialRoute) { return GoRouter( initialLocation: initialRoute, routes: [ GoRoute( path: '/home', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/product/:productId', builder: (context, state) { final productId = state.pathParameters['productId']!; return ProductDetailScreen(productId: productId); }, ), GoRoute( path: '/category/:categoryId', builder: (context, state) { final categoryId = state.pathParameters['categoryId']!; return CategoryScreen(categoryId: categoryId); }, ), GoRoute( path: '/profile/:userId', builder: (context, state) { final userId = state.pathParameters['userId']!; return UserProfileScreen(userId: userId); }, ), ], ); }
Code Example

Complete onboarding example

import 'package:flutter/material.dart'; import 'package:redirectly/redirectly.dart'; import 'package:go_router/go_router.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize Redirectly await Redirectly.initialize( apiKey: 'your_api_key_here', ); // Get deferred deep link final initialLink = await Redirectly.getInitialLink(); // Determine initial route String initialRoute = '/home'; if (initialLink != null) { initialRoute = _getRouteFromDeepLink(initialLink); } runApp(MyApp(initialRoute: initialRoute)); } String _getRouteFromDeepLink(RedirectlyLink link) { if (link.path == 'product') { final productId = link.queryParameters['product']; return '/product/$productId'; } else if (link.path == 'category') { final categoryId = link.queryParameters['category']; return '/category/$categoryId'; } return '/home'; } class MyApp extends StatelessWidget { final String initialRoute; const MyApp({required this.initialRoute}); @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: GoRouter( initialLocation: initialRoute, routes: [ GoRoute( path: '/home', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/product/:productId', builder: (context, state) { final productId = state.pathParameters['productId']!; return ProductScreen(productId: productId); }, ), GoRoute( path: '/category/:categoryId', builder: (context, state) { final categoryId = state.pathParameters['categoryId']!; return CategoryScreen(categoryId: categoryId); }, ), ], ), ); } } class ProductScreen extends StatelessWidget { final String productId; const ProductScreen({required this.productId}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Product')), body: FutureBuilder<Product>( future: fetchProduct(productId), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.loading) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } if (!snapshot.hasData) { return const Center(child: Text('Product not found')); } final product = snapshot.data!; return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.network(product.imageUrl), Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( product.name, style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 8), Text( '$${product.price}', style: Theme.of(context).textTheme.headlineSmall?.copyWith( color: Colors.green, ), ), const SizedBox(height: 16), ElevatedButton( onPressed: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Added to cart!')), ); }, child: const Text('Add to Cart'), ), ], ), ), ], ), ); }, ), ); } Future<Product> fetchProduct(String id) async { // Fetch product details from your API // This is called immediately on app launch return Product( id: id, name: 'Sample Product', price: 29.99, imageUrl: 'https://example.com/product.jpg', ); } } class Product { final String id; final String name; final double price; final String imageUrl; Product({ required this.id, required this.name, required this.price, required this.imageUrl, }); }

This example shows:

  • Deferred link retrieval on app launch
  • Dynamic routing based on deep link content
  • go_router integration for seamless navigation
  • Data loading while showing the right screen
FAQ

Frequently asked questions

What if the user has the app already installed?

If the app is installed, the deep link opens immediately without needing deferred link lookups. It's instant. Deferred links are a safety net for when the app isn't installed yet.

Can I personalize the onboarding screen itself?

Yes. You can pass context parameters in the deep link to show different onboarding flows. For example, new e-commerce users could see product tips, while social users see connection suggestions.

How do I handle invalid product IDs in the deep link?

Check if the product exists before navigating. If invalid, navigate to home instead. The user can always find the product through your app's normal search/browse.

Does this work with analytics?

Yes. You can log which deep links users arrived from, track how often each page is a first impression, and measure conversion from personalized onboarding.

Can I A/B test onboarding flows?

Absolutely. Pass an experiment variant in the deep link, and show different UX based on it. Measure which onboarding flow converts better.

Try Redirectly Free

Get a free API key, subdomain, and 10k monthly links.