Next.js Guide

How to set up Tamagui with Next.js

You should run npm create tamagui which bootstraps a very nicely configured Next.js app where you can take or leave whatever you want.

Also check out the source for this site  to see a good example of a full featured Next.js website as well, especially the next.config.js and tamagui.config.ts.

next.config.js

You'll want to add the @tamagui/next-plugin and set it up in your next config. See the compiler install docs for more options:

module.exports = function (name, { defaultConfig }) {
let config = {
...defaultConfig,
// ...your configuration
}
const tamaguiPlugin = withTamagui({
config: './tamagui.config.ts',
components: ['tamagui'],
})
return {
...config,
...tamaguiPlugin(config),
}
}

_document.tsx

You'll want to set up a _document.tsx and gather both the react-native-web style object using AppRegistry, as well as Tamagui styles using Tamagui.getCSS() into the head element.

Make sure the Tamagui style tag always comes after the getStyleElement style tag to ensure Tamagui styles override built-in react-native-web ones.

import NextDocument, { Head, Html, Main, NextScript } from 'next/document'
import { Children } from 'react'
import { AppRegistry } from 'react-native'
import Tamagui from '../tamagui.config'
export default class Document extends NextDocument {
static async getInitialProps({ renderPage }) {
AppRegistry.registerComponent('Main', () => Main)
const page = await renderPage()
// @ts-ignore
const { getStyleElement } = AppRegistry.getApplication('Main')
const styles = [
getStyleElement(),
<style dangerouslySetInnerHTML={{ __html: Tamagui.getCSS() }} />,
]
return { ...page, styles: Children.toArray(styles) }
}
render() {
return (
<Html>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

Themes

We've created a package that works with Tamagui to properly support SSR light/dark themes that also respect user system preference, called @tamagui/next-theme. It assumes your light/dark themes are named as such, but you can override it. This is pre-configured in the create-tamagui starter.

We recommend memo-ing children so they don't re-render. Here's how you'd set up your _app.tsx:

import { NextThemeProvider, useRootTheme } from '@tamagui/next-theme'
import { AppProps } from 'next/app'
import Head from 'next/head'
import React, { useMemo, useState } from 'react'
import { TamaguiProvider } from 'tamagui'
import config from '../tamagui.config'
export default function App({ Component, pageProps }: AppProps) {
const [theme, setTheme] = useRootTheme()
// memo to avoid re-render on dark/light change
const contents = useMemo(() => {
return <Component {...pageProps} />
}, [pageProps])
// because we do our custom getCSS() above, we disableInjectCSS here
return (
<>
<Head>{/* ... */}</Head>
<NextThemeProvider onChangeTheme={setTheme}>
<TamaguiProvider config={config} disableInjectCSS disableRootThemeClass defaultTheme={theme} >
{contents}
</TamaguiProvider>
</NextThemeProvider>
</>
)
}

If you need to access the current theme, say for a toggle button, you will then use the useTheme hook. We'll release an update in the future that makes this automatically work better with Tamagui's built-in useTheme.

import { useTheme } from '@tamagui/next-theme'
export default () => {
const { theme, toggleTheme } = useTheme()
return <Button>Change theme (currently: {theme})</Button>
}

Mount animations

Animations that run through JS drivers and have enterStyle set will want to add the following script that allows for hiding those animations before the JS runs. This is the "right way" to handle things, as it allows for disabling JS entirely and not accidentally hiding all unmounted things. Meanwhile, it still properly avoids a flash of mounted style for SSR pages.

Add this to your _app.tsx render output:

<NextHead>
<script key="tamagui-animations-mount" dangerouslySetInnerHTML={{ // avoid flash of animated things on enter __html: `document.documentElement.classList.add('t_unmounted')`, }} />
</NextHead>