Skip to main content

Next.js App Router

Solito supports the Next.js App Router as of v4. Keep in mind that React Native Web components are all client components, so you will likely find yourself using use client often.

How it works​

Solito introduces a number of changes in v4 to support the App Router. All previous code and docs still work with pages. However, you will need to use the new hooks from solito/navigation to access the router and URL parameters.

Link and TextLink components are now marked with use client, making them compatible with RSC and the App Router.

See the docs for Link.

solito/image​

SolitoImage is now marked with use client, making it compatible with RSC and the App Router.

See the docs for Image.

solito/moti​

To use the MotiLink component in the App Directory, you should import it from solito/moti/app.

solito/navigation​

Following the Next.js approach, there is now a new import, solito/navigation, for the App Router hooks. You will use this instead of other imports you may be used to, such as solito/router for useRouter.

See the docs for all hooks.

The following hooks are added:

  • useRouter: Similar to the useRouter hook from solito/router, adapted for the App Router by following the next/navigation conventions. It has the same behavior on iOS and Android.
  • useParams: Read dynamic segment parameters (/users/[userId]) as { userId: string }.
  • useSearchParams: Read URL search parameters using the URLSearchParams class.
  • usePathname: The much-requested hook to read the current path. On native, this is only safe if you're using Expo Router. This hook may change in the future, depending on its stability on native.
  • useUpdateSearchParams: A convenience hook to update search parameters as an object. Calls setParams on native, and router.push or router.replace on Web (configurable).
  • useLink The same hook that exists now from solito/link, adapted for the App Router. You can call this to create accessible, custom link components.

Setup​

In addition to using new code, you'll need to add some configurations to the root layout component in the app folder.

/app/layout.tsx​

Clear out the layout.tsx file to look like this:

tsx
import { StylesProvider } from './styles-provider'
import './globals.css'
export const metadata = {
title: 'Create Solito App',
description: 'Generated by create Solito app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<StylesProvider>{children}</StylesProvider>
</body>
</html>
)
}
tsx
import { StylesProvider } from './styles-provider'
import './globals.css'
export const metadata = {
title: 'Create Solito App',
description: 'Generated by create Solito app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<StylesProvider>{children}</StylesProvider>
</body>
</html>
)
}

Notice that we have two imports at the top. Let's implement those next.

/app/styles-provider.tsx​

StylesProvider should look like this (you can copy-paste it):

Click to view code
tsx
// @ts-nocheck
'use client'
import { useServerInsertedHTML } from 'next/navigation'
import { StyleSheet } from 'react-native'
export function StylesProvider({ children }: { children: React.ReactNode }) {
useServerInsertedHTML(() => {
const sheet = StyleSheet.getSheet()
return (
<style
dangerouslySetInnerHTML={{ __html: sheet.textContent }}
id={sheet.id}
/>
)
})
return <>{children}</>
}
tsx
// @ts-nocheck
'use client'
import { useServerInsertedHTML } from 'next/navigation'
import { StyleSheet } from 'react-native'
export function StylesProvider({ children }: { children: React.ReactNode }) {
useServerInsertedHTML(() => {
const sheet = StyleSheet.getSheet()
return (
<style
dangerouslySetInnerHTML={{ __html: sheet.textContent }}
id={sheet.id}
/>
)
})
return <>{children}</>
}

/app/globals.css​

Finally, add this CSS reset:

Click to view code
css
html,
body,
#__next {
width: 100%;
/* To smooth any scrolling behavior */
-webkit-overflow-scrolling: touch;
margin: 0px;
padding: 0px;
/* Allows content to fill the viewport and go beyond the bottom */
min-height: 100%;
}
#__next {
flex-shrink: 0;
flex-basis: auto;
flex-direction: column;
flex-grow: 1;
display: flex;
flex: 1;
}
html {
scroll-behavior: smooth;
/* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
-webkit-text-size-adjust: 100%;
height: 100%;
}
body {
display: flex;
/* Allows you to scroll below the viewport; default value is visible */
overflow-y: auto;
overscroll-behavior-y: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-ms-overflow-style: scrollbar;
}
css
html,
body,
#__next {
width: 100%;
/* To smooth any scrolling behavior */
-webkit-overflow-scrolling: touch;
margin: 0px;
padding: 0px;
/* Allows content to fill the viewport and go beyond the bottom */
min-height: 100%;
}
#__next {
flex-shrink: 0;
flex-basis: auto;
flex-direction: column;
flex-grow: 1;
display: flex;
flex: 1;
}
html {
scroll-behavior: smooth;
/* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
-webkit-text-size-adjust: 100%;
height: 100%;
}
body {
display: flex;
/* Allows you to scroll below the viewport; default value is visible */
overflow-y: auto;
overscroll-behavior-y: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-ms-overflow-style: scrollbar;
}

That's it!

_document.tsx/_app.tsx​

You can start using the App Router with Solito without changing those existing files.

Follow the Next.js Docs​

Please reference the official Next.js docs for using the App Router.

Example​

Here is a minimal repo with the App Directory and React Native Web to reference. It is not a starter app necessarily.