Welcome to Day 8 of the React Native Advent Calendar!
In Day 1, we covered the core features of Expo Router. Today, we’re diving into 5 advanced features that take your apps to the next level - from server components to native interactions.
6. React Server Components (Beta)
You thought this is only a web thing? Think again!
You can now render React Native components on the server! (Available in SDK 52+)
'use server';
import { Text } from 'react-native';
export default async function renderInfo({ name }) {
// Securely fetch data from an API, and read environment variables...
return <Text>Hello, {name}!</Text>;
}The integration is seamless adn feels like you are using any other component, just wrap your component in a React.Suspense component and pass the component as a child.
import React from 'react';
import { ActivityIndicator } from 'react-native';
import renderInfo from '../actions/render-info';
export default function Index() {
return (
<React.Suspense
fallback={
// The view that will render while the Server Function is awaiting data.
<ActivityIndicator />
}
>
{renderInfo({ name: 'World' })}
</React.Suspense>
);
}Key capabilities:
- Async data fetching with React Suspense
- Secure access to environment variables and server APIs
- Reduced JavaScript bundle size
- Server-side rendering for SEO
⚠️ Note: This is experimental and not recommended for production yet.
📖 Read the full documentation on how to get started
🎥 Video: Your First RSC with React Native (From Start to Deploy)
7. Headless UI (Custom Tabs)
Last time we saw the Native Tabs component, but you can also build completely custom tab bars with the unstyled Tabs component from Expo Router.
By doing this, you get full control over styling and behavior.
import { Tabs, TabSlot, TabList, TabTrigger } from 'expo-router/ui';
export default function CustomTabsLayout() {
return (
<Tabs>
<TabSlot />
<TabList style={styles.tabBar}>
<TabTrigger name="home" style={styles.trigger}>
{({ isFocused }) => (
<>
<Icon name="home" color={isFocused ? 'blue' : 'gray'} />
<Text style={isFocused && styles.activeText}>Home</Text>
</>
)}
</TabTrigger>
<TabTrigger name="profile" style={styles.trigger}>
{({ isFocused }) => (
<>
<Icon name="person" color={isFocused ? 'blue' : 'gray'} />
<Text style={isFocused && styles.activeText}>Profile</Text>
</>
)}
</TabTrigger>
</TabList>
</Tabs>
);
}Customization approaches:
- Styling - Use style props for simple customizations
- Component replacement - Use
asChildprop for custom implementations - Hook-based - Access hooks for complete render tree control
This approach is also great for implementing a custom web behavior where a native tab bar isn’t fitting.
8. Static Site Generation (SSG)
Export your Expo Router app as a static website for deployment to any hosting service!
Simply add the output property to your app.json file and set it to static.
{
"expo": {
"web": {
"bundler": "metro",
"output": "static"
}
}
}You can now generate a static site by running the following command:
# Generate static site
npx expo export --platform web
# Output goes to dist/ directoryJust like other SSG tools, you can also generate dynamic routes by using the generateStaticParams function.
export async function generateStaticParams(): Promise<{ slug: string }[]> {
const posts = await getAllBlogPosts();
return posts.map((post) => ({
slug: post.slug
}));
}
export default function BlogPost() {
const { slug } = useLocalSearchParams();
// Render blog post...
}A static export is of course especially useful for SEO, as it’s a lot easier to index by search engines, and you can also add meta tags to your pages.
import { Head } from 'expo-router';
export default function Page() {
return (
<>
<Head>
<title>My Page Title</title>
<meta name="description" content="Page description for SEO" />
<meta property="og:image" content="/preview.jpg" />
</Head>
<View>{/* Your content */}</View>
</>
);
}Features:
- Deploy to EAS Hosting, Netlify, Vercel, Cloudflare Pages, or any static host
- Automatic font optimization with
expo-font - Custom HTML wrapper via
app/+html.tsx - Place static assets in
public/directory
9. Deep Links & Native Intent
Although Expo Router has built-in support for deep links, you can also handle incoming URLs before your app fully loads with +native-intent - one of those cool additions that can be unbelievable helpful when you know about it!
export function redirectSystemPath({ path, initial }: { path: string; initial: boolean }) {
// Handle legacy URLs
if (path.startsWith('/old-route/')) {
return path.replace('/old-route/', '/new-route/');
}
// Handle third-party referral links
if (path.includes('?ref=')) {
const url = new URL(path, 'https://placeholder.com');
const ref = url.searchParams.get('ref');
// Track referral and redirect
return `/welcome?referral=${ref}`;
}
return path;
}Use cases:
- Transform outdated deep links from old app versions
- Process third-party referral URLs
- Handle platform-specific URL schemes
- Redirect based on URL patterns
Example: Handling share links
export function redirectSystemPath({ path }) {
// Share link format: myapp://share/post/123
if (path.startsWith('/share/post/')) {
const postId = path.split('/').pop();
return `/posts/${postId}`;
}
return path;
}Important: This is native-only and runs without app context (no auth state, no user data).
10. Link Preview (iOS Peek and Pop)
This is a feature from Expo Router v5, and it gives you the ability to preview the destination of a link before you actually navigate to it - which gives iOS users a native peek-and-pop experience! (iOS only, SDK 54+)
You can use it with any Expo Router Link component.
import { Link } from 'expo-router';
export default function PostsList() {
return (
<ScrollView>
{posts.map((post) => (
<Link.Trigger key={post.id} href={`/posts/${post.id}`}>
<View style={styles.postCard}>
<Text style={styles.title}>{post.title}</Text>
<Text style={styles.excerpt}>{post.excerpt}</Text>
</View>
<Link.Preview>
<PostPreview post={post} />
</Link.Preview>
</Link.Trigger>
))}
</ScrollView>
);
}You can also spice this up by adding a context menu to the link.
<Link.Trigger href="/post">
<Text>View Post</Text>
<Link.Preview>
<PostPreview />
</Link.Preview>
<Link.ContextMenu>
<Link.ContextMenuItem title="Share" icon="share" />
<Link.ContextMenuItem title="Save" icon="bookmark" />
</Link.ContextMenu>
</Link.Trigger>UX Benefits:
- Users can preview destinations before committing to navigation
- Reduces accidental clicks
- Feels native and familiar to iOS users
- Improves content discovery
Wrapping Up
Expo Router isn’t just about file-based routing—it’s a complete navigation and architectural solution that brings web-like patterns to React Native. From server components to native-feeling tabs, these advanced features show how modern React Native development can be both powerful and developer-friendly.
What’s the most exciting feature for you? Let me know on Twitter!
See you again tomorrow for Day 9! 🎄
What to explore next:
- 🎯 Beginner? Start with my Expo Router Quickstart Course
- 🚀 Ready to launch your first app? Check out my Zero to Hero React Native Mission
- 🎥 Video learner? Watch my Tutorials on YouTube