Overview of Solito
masterexpo-next-react-navigation, featuring a brand new API and architectural approach.repository·master·Indexed 23 days ago
https://github.com/nandorojo/solitoA library that unifies React Native and Next.js, enabling developers to share code between mobile (via React Native/Expo) and web (via Next.js) applications with a focus on unified navigation. It includes the create-solito-app CLI for scaffolding monorepos and provides navigation hooks like useRouter, useParams, and usePathname for the Next.js App Router.
expo-next-react-navigation, featuring a brand new API and architectural approach.Solito is a library designed to bridge React Native and Next.js for cross-platform application development. It provides two primary capabilities:
react-navigation and next/router that allows you to write navigation logic once and share it across both native (Expo/React Native) and web (Next.js) platforms.Solito avoids the complexity of mixing navigation libraries by letting each platform use its native standard. It ensures that code for one platform is never imported into the other:
next/router and next/link.React Navigation.This approach prevents the 'two sources of truth' problem found in other unification attempts and keeps bundle sizes optimized by ensuring React Navigation is never imported on Web, and Next.js code is never imported on Native.
The Solito starter uses an opinionated folder structure to separate platform entry points from shared logic.
apps/: Contains the entry points for each platform.expo/: The React Native / Expo application.next/: The Next.js web application.packages/: Contains shared code used by both apps.app/: The primary location for shared logic. Most files should be imported from here.features/: Organize code by feature here (avoid using a screens folder).provider/: Contains app providers and web-specific no-ops.navigation/: Contains navigation-related code for React Native (since RN lacks a pages/ directory like Next.js).The project is organized into apps (entry points) and packages (shared code):
apps/expo/app: File system routing for iOS and Android.apps/next: Web entry point.packages/app/features: Shared business logic and features (organized by feature, not by screens).packages/app/provider: App-wide providers (including no-ops for Web).packages/app/design: The design system, including typography and layout components.In Next.js, you can use shallow routing to open a page as a modal. This allows you to change the URL and render a component on top of the current view without unmounting the background content (preserving scroll position and local state).
router.push with the shallow: true option.as parameter (the second argument in router.push) to define the clean URL for the user.createParam to detect the presence of the parameter and conditionally render the Modal.On Native, Solito will interpret the as path and open the corresponding screen in a navigation stack.
// 1. In your shared feature component:
export function SearchList() {
const router = useRouter()
return (
<ArtistResults
onPressArtist={(artist) => {
// Use shallow routing to open the artist as a modal
router.push(`/search?artistSlug=${artist.slug}`, `/@${artist.slug}`, {
shallow: true,
})
}}
/>
)
}
// 2. In your Next.js page component:
import { createParam } from 'solito'
import { Modal } from 'react-native'
import dynamic from 'next/dynamic'
const ArtistPage = dynamic(() => import('../artist/[artistSlug]'))
const { useParam } = createParam<{ artistSlug?: string }>()
export default function SearchPage() {
const [artistSlug] = useParam('artistSlug')
return (
<>
<SearchList />
<Modal visible={Boolean(artistSlug)}>
<ArtistPage />
</Modal>
</>
)
}Solito is recommended in the following scenarios:
useRouter and Link) plus utilities like useParam, while ensuring any native-only code is tree-shaken.To automate mobile app updates using Expo's GitHub Actions in a Solito monorepo, you must specify the working-directory in your workflow file. This ensures the eas update command runs from the correct directory (typically ./apps/expo) rather than the monorepo root.
- name: Publish update
run: eas update --auto
working-directory: ./apps/expoTo create animated, accessible links using moti's MotiPressable component, you should use Solito's useLink hook instead of the standard <Link /> component. This allows you to spread the link properties (like href and as) directly onto the MotiPressable component, enabling animations based on hover and pressed states while maintaining navigation functionality.
import { MotiPressable } from 'moti/interactions'
import { useLink, UseLinkProps } from 'solito/link'
type MotiLinkProps = UseLinkProps &
Omit<React.ComponentProps<typeof MotiPressable>, keyof UseLinkProps>
export function MotiLink({
as,
href,
shallow,
children,
...motiPressableProps
}: MotiLinkProps) {
const linkProps = useLink({
as,
href,
shallow,
})
return (
<MotiPressable {...motiPressableProps} {...linkProps}>
{children}
</MotiPressable>
)
}Solito provides MotiLink for first-class integration with the moti animation library. It behaves like a standard Link (showing URLs in browsers and supporting Command + Click) but allows you to use Moti's animation features. It is built on top of MotiPressable and supports animations based on hovered and pressed states using Reanimated worklets, which avoids unnecessary React re-renders.
import { MotiLink } from 'solito/moti'
// Example: Animating scale on hover and press
const ButtonLink = ({ children, href, as }) => (
<MotiLink
href={href}
as={as}
animate={({ hovered, pressed }) => {
'worklet'
return {
scale: pressed ? 0.9 : hovered ? 1.1 : 1,
}
}}
>
{children}
</MotiLink>
)
// Example: Simple fade-in animation
<MotiLink href="/users/fernando" from={{ opacity: 0 }} animate={{ opacity: 1 }}>
{children}
</MotiLink>To use MotiLink, you must have moti installed in your project.
Requirement:
moti version 0.0.18 or higher must be installed.Solito uses a "headless navigation" approach where URLs are the single source of truth. This allows you to share 100% of your screen logic and navigation logic across Web and Native platforms while allowing each platform to handle the actual rendering (the "skeleton") differently.
<ArtistScreen />) are identical across iOS, Android, and Web.pages directory (e.g., /pages/artist/[slug]).Solito maps URLs to screens. When you use a component like <Link href="/users/fernando" />, Solito detects the platform and determines whether to trigger a Next.js route change or a React Navigation transition. The platforms (Next.js and React Native) remain isolated and do not communicate with each other directly.