google-agents-cli-scaffold
Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
expo-router
Expo Router file-based routing for Universal React Native applications. Trigger: When working with Expo Router navigation, file-based routes, layouts, tabs, modals, typed routes, or understanding how routing works in Liftera mobile app.
Full skill instructions
Expo Router is a file-based router for Universal React Native applications. It brings the best file-system routing concepts from the web to native applications, allowing your routing to work seamlessly across Android, iOS, and web platforms.
app/ directory, it automatically becomes a routeExpo Router uses special file naming conventions to define routing behavior:
app/
├── home.tsx # /home
├── feed/
│ └── favorites.tsx # /feed/favorites
Regular file and directory names create static routes. The URL matches exactly as it appears in the file tree.
app/
├── [userName].tsx # /evanbacon, /expo, etc.
├── products/
│ └── [productId]/
│ └── index.tsx # /products/123
Square brackets [param] create dynamic segments. The parameter is accessible via useLocalSearchParams() hook:
// app/[userName].tsx
import { useLocalSearchParams } from "expo-router";
export default function UserProfile() {
const { userName } = useLocalSearchParams<{ userName: string }>();
// userName = "evanbacon" when visiting /evanbacon
}
app/
├── (tabs)/
│ ├── index.tsx # / (not /tabs)
│ └── settings.tsx # /settings (not /tabs/settings)
├── (auth)/
│ ├── login.tsx # /login (not /auth/login)
│ └── signup.tsx # /signup
Parentheses (group) create route groups that organize files without affecting the URL structure. Critical for:
app/
├── index.tsx # / (root)
├── profile/
│ └── index.tsx # /profile
index.tsx files define the default route for a directory. Like index.html on the web.
app/
├── _layout.tsx # Root layout (wraps everything)
├── (tabs)/
│ ├── _layout.tsx # Tab navigator layout
│ └── index.tsx
_layout.tsx files are not routes themselves but define how routes inside their directory relate to each other:
Critical: The root app/_layout.tsx is where initialization code goes (previously in App.jsx).
app/
├── +not-found.tsx # 404 handler
├── +html.tsx # Web HTML customization
├── +native-intent.tsx # Deep link handler
├── +middleware.ts # Request middleware
Routes with + prefix have special meaning:
+not-found: Catches unmatched routes (404)+html: Customizes HTML boilerplate on web+native-intent: Handles third-party deep links+middleware: Runs code before route rendersA stack is the foundational navigation pattern. Routes animate on top of each other:
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="details" options={{ title: 'Details' }} />
<Stack.Screen
name="modal"
options={{ presentation: 'modal' }}
/>
</Stack>
);
}
How it works: Each <Stack.Screen> corresponds to a file in the same directory. The name prop matches the filename (without extension).
Tabs create a persistent bottom navigation bar (iOS/Android) or top tabs:
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: 'blue' }}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <HomeIcon color={color} />
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color }) => <UserIcon color={color} />
}}
/>
</Tabs>
);
}
File structure:
app/
├── _layout.tsx # Root stack
└── (tabs)/ # Route group (no URL segment)
├── _layout.tsx # Tab navigator
├── index.tsx # First tab (/)
└── profile.tsx # Second tab (/profile)
Why route groups? The (tabs) group prevents /tabs/profile and creates clean URLs like /profile.
Layouts can be nested to create complex navigation hierarchies:
app/
├── _layout.tsx # Root Stack
└── (tabs)/ # Route group
├── _layout.tsx # Tabs (Home, Feed, Profile)
├── index.tsx # Home tab
├── feed/
│ ├── _layout.tsx # Stack inside Feed tab
│ ├── index.tsx # Feed list
│ └── [id].tsx # Feed detail (pushed on stack)
└── profile.tsx # Profile tab
Navigation flow:
import { router } from "expo-router";
// Push new route on stack
router.push("/profile");
router.push({ pathname: "/user/[id]", params: { id: "123" } });
// Replace current route (no back button)
router.replace("/login");
// Go back
router.back();
// Navigate to specific route in stack
router.navigate("/settings");
// Dismiss modal/stack
router.dismiss();
router.dismissAll();
import { Link } from 'expo-router';
// Static route
<Link href="/profile">Go to Profile</Link>
// Dynamic route
<Link href={{ pathname: '/user/[id]', params: { id: userId } }}>
View User
</Link>
// Replace instead of push
<Link href="/login" replace>Login</Link>
import { usePathname, useSegments, useLocalSearchParams } from "expo-router";
// Current path
const pathname = usePathname(); // "/feed/123"
// Path segments
const segments = useSegments(); // ["feed", "123"]
// Route parameters
const { id } = useLocalSearchParams<{ id: string }>();
Liftera has experiments.typedRoutes: true in app.json, enabling compile-time route validation.
app/ directory.expo/types/router.d.ts with all valid routeshref props at compile timeimport { router, Link } from 'expo-router';
// ✅ Valid routes - TypeScript happy
<Link href="/profile" />
<Link href="/feed/favorites" />
<Link href={{ pathname: '/user/[id]', params: { id: '123' } }} />
// ❌ TypeScript errors - route doesn't exist
<Link href="/profle" /> // Typo caught at compile time
<Link href="/invalid" />
// ❌ TypeScript errors - missing required params
<Link href="/user/[id]" /> // Must use object form with params
// ❌ TypeScript errors - invalid params
<Link href={{ pathname: '/user/[id]', params: { userId: '123' } }} />
// Param name must be 'id', not 'userId'
Modals are routes presented differently, not separate components.
// app/_layout.tsx
<Stack>
<Stack.Screen name="index" />
<Stack.Screen
name="modal"
options={{
presentation: 'modal', // Modal presentation
headerShown: false,
animation: 'slide_from_bottom'
}}
/>
</Stack>
Navigate to modal:
router.push("/modal"); // Presents as modal
router.back(); // Dismisses modal
Deep linking: Modals work with deep links. Opening myapp://modal presents the modal correctly.
Every route is automatically deep linkable:
// app.json
{
"expo": {
"scheme": "liftera"
}
}
URL mapping:
liftera:// → / (index)liftera://profile → /profileliftera://user/123 → /user/[id] with id: "123"Web URLs (when deployed):
https://liftera.app/ → /https://liftera.app/profile → /profileSame code, all platforms - no platform-specific linking logic needed.
// app/_layout.tsx
import { useAuth } from '@/hooks/useAuth';
import { Redirect, Slot } from 'expo-router';
export default function RootLayout() {
const { user, loading } = useAuth();
if (loading) return <LoadingScreen />;
if (!user) return <Redirect href="/login" />;
return <Slot />;
}
How it works: Root layout checks auth state. Unauthenticated users are redirected before any child routes render.
// app/(app)/_layout.tsx - Authenticated app
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
// app/(auth)/_layout.tsx - Auth flow
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="login" />
<Stack.Screen name="signup" />
</Stack>
Route groups let you define different layout contexts for different parts of your app.
// app/_layout.tsx
export { ErrorBoundary } from 'expo-router';
// Or custom
export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
return (
<View>
<Text>Error: {error.message}</Text>
<Button onPress={retry}>Try Again</Button>
</View>
);
}
Automatic error handling: Errors in routes are caught and displayed without crashing the app.
.tsx extension for TypeScript routes.js - Liftera is TypeScript-first_layout.tsx when using Stack/Tabsapp/_layout.tsx - it's required<Slot /> in layouts without navigatorsuseLocalSearchParams<T>()(group) for organizing without URL impactProject scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
Set up tracing, logging, and monitoring for deployed ADK agents across Cloud Trace, BigQuery, and third-party platforms.
Enterprise Azure infrastructure architect generating Bicep or Terraform from workload descriptions.
Plan and configure production-ready Azure Kubernetes Service clusters with Day-0 and Day-1 best practices.
Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics. Rigid grids, extreme type scale contrast, utilitarian color, analog degradation effects. For data-heavy dashboards, portfolios, or editorial sites that need to feel like declassified blueprints.
Web search, scraping, extraction, crawling, and monitoring via ScrapeGraph AI CLI.
Skill for working with Firebase Hosting (Classic). Use this when you want to deploy static web apps, Single Page Apps (SPAs), or simple microservices. Do NOT use for Firebase App Hosting.
Deploy and manage web apps with Firebase App Hosting. Use this skill when deploying Next.js/Angular apps with backends.
Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".
Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.
Design and build isolated, reusable Convex backend components with clear boundaries and app-facing wrappers.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Opus.ai: Revolutionize Your Web Experience
Build a no-code AI app in minutes.
An IDE for code migration from legacy to modern frameworks through coding agents.
Automate CGI animation in live-action scenes
Branded artistic QR-code concepts
Launch a website in seconds with AI.
Streamline Your Coding Experience with AI Code Helper
Convert any screenshot or design to clean code.