Next.js Integration Guide for MeoNode UI
Overview
@meonode/ui is a modern, type-safe React UI library designed for seamless integration with popular frameworks, especially Next.js. This guide provides a comprehensive, step-by-step walkthrough for setting up a Next.js project with MeoNode UI, demonstrating real-world integration patterns with the Context-based theming system and the Portal system.
Next.js Integration
Installation & Setup
Begin your project by using the recommended Next.js CLI to set up a new application. This ensures your project is pre-configured with the latest best practices.
# Create a new Next.js app with TypeScript support npx create-next-app@latest my-app --typescript # Navigate to your project directory cd my-app # Install the core MeoNode UI library and peer dependencies yarn add @meonode/ui @emotion/cache @emotion/react @emotion/styled
Next.js Configuration
Configure Emotion's CSS-in-JS functionality and optimize imports.
import type { NextConfig } from 'next' const nextConfig: NextConfig = { // Enables Emotion's CSS-in-JS features compiler: { emotion: true, }, // Optimizes module imports to improve build performance experimental: { optimizePackageImports: ['@meonode/ui'], }, } export default nextConfig
Theme Configuration (src/constants/themes/)
Define your design tokens in a central system and create light/dark theme variants. Each theme must include a mode
and a system object containing your tokens.
const themeSystem = { text: { xs: '0.75rem', sm: '0.875rem', md: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem', }, radius: { sm: '2px', md: '4px', lg: '8px', xl: '16px', full: '9999px', }, } export default themeSystem
import { Theme } from '@meonode/ui' import themeSystem from './themeSystem' const lightTheme: Theme = { mode: 'light', system: { ...themeSystem, primary: { default: '#2196F3', content: '#FFFFFF' }, secondary: { default: '#9C27B0', content: '#FFFFFF' }, base: { default: '#FFFFFF', content: '#1A1A1A' }, }, } export default lightTheme
Store (src/redux/store.ts)
This file sets up a singleton Redux store, making it accessible from both the server and client in a Next.js environment.
import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' import appSlice from '@src/redux/slice/app.slice' import { createNode } from '@meonode/ui' import { Provider } from 'react-redux' export const initializeStore = (preloadedState?: object) => { const store = configureStore({ reducer: { app: appSlice.reducer, }, middleware: getDefaultMiddleware => getDefaultMiddleware() .concat // your middleware (), preloadedState, }) setupListeners(store.dispatch) return store } export type RootState = ReturnType<ReturnType<typeof initializeStore>['getState']> export type AppDispatch = ReturnType<typeof initializeStore>['dispatch'] export const ReduxProvider = createNode(Provider)
Wrapper Components (src/components/Wrapper.ts)
The Wrapper component is the heart of your client-side providers, integrating Redux, the Theme system, and the Portal System. Keep StyleRegistry out of Wrapper — mount it once in the root layout instead (see below).
'use client' import { Children, Node, PortalHost, PortalProvider, Theme, ThemeProvider } from '@meonode/ui' import { StrictMode, useMemo } from 'react' import { CssBaseline } from '@meonode/mui' import darkTheme from '@src/constants/themes/darkTheme' import lightTheme from '@src/constants/themes/lightTheme' import { initializeStore, ReduxProvider, RootState } from '@src/redux/store' export const Wrapper = ({ preloadedState, themeMode, children, }: { preloadedState?: Partial<RootState> themeMode?: Theme['mode'] children?: Children isPortal?: boolean }) => { const store = useMemo(() => initializeStore(preloadedState), [preloadedState]) // Resolve theme from the server-passed cookie value so SSR and hydration agree. const theme = useMemo(() => (themeMode === 'dark' ? darkTheme : lightTheme), [themeMode]) return Node(StrictMode, { children: PortalProvider({ children: [ CssBaseline(), ReduxProvider({ store, children: ThemeProvider({ theme, children: Array.isArray(children) ? children.concat(PortalHost()) : [children, PortalHost()], }), }), ], }), }).render() }
App Router Integration (src/app/layout.ts)
This is the main layout file for Next.js. It uses userAgent to detect devices for preloaded state, reads the theme cookie on the server, and wraps the entire app in StyleRegistry at the root layout so every route segment shares one Emotion cache during SSR.
import type { Metadata } from 'next' import { Geist, Geist_Mono } from 'next/font/google' import './globals.css' import { Body, Html, Node } from '@meonode/ui' import { StyleRegistry } from '@meonode/ui/nextjs-registry' import { cookies, headers } from 'next/headers' import { ReactNode } from 'react' import { RootState } from '@src/redux/store' import { Wrapper } from '@src/components/Wrapper' import { userAgent } from 'next/server' const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'], }) const geistMono = Geist_Mono({ variable: '--font-geist-mono', subsets: ['latin'], }) export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app', } export default async function RootLayout({ children }: { children: ReactNode }) { const reqHeaders = await headers() const ua = userAgent({ headers: reqHeaders }) const isMobile = ua.device.type === 'mobile' || ua.device.type === 'tablet' const cookieStore = await cookies() const themeMode = cookieStore.get('theme')?.value as 'light' | 'dark' const preloadedState: RootState = { app: { isMobile, }, } return Html({ lang: 'en', className: themeMode === 'dark' ? 'dark-theme' : 'light-theme', 'data-theme': themeMode, children: Body({ className: `${geistSans.variable} ${geistMono.variable} font-sans`, children: StyleRegistry({ children: Node(Wrapper, { preloadedState, themeMode, children, }), }), }), }).render() }
Nested route layouts (docs, dashboards, etc.)
Keep metadata and data fetching in server layout.ts files, but move styled MeoNode chrome (sidebars, navbars, page frames with Root / Column / Row) into 'use client' shell components. Render them from the server layout with Node(MyShell, { children }).render().
Without that client boundary, nested server layouts can SSR styled nodes outside the root StyleRegistry cache and cause hydration mismatches or broken layout — even when StyleRegistry is correctly placed at the root.
'use client' import { Column, Main, Node, Root, Row } from '@meonode/ui' import type { ReactNode } from 'react' export default function DocsSectionShell({ children }: { children?: ReactNode }) { return Root({ backgroundColor: 'theme.base', color: 'theme.base.content', children: [ /* Navbar, Sidebar, etc. */ Main({ children }), ], }).render() }
import { Node } from '@meonode/ui' import DocsSectionShell from '@src/components/DocsSectionShell' export default function DocsLayout({ children }: { children: React.ReactNode }) { return Node(DocsSectionShell, { children }).render() }
Page Components & Portals (src/app/page.ts)
Use the usePortal hook for managing overlays. Modals are standard components that receive close and data props.
'use client' import { Center, Column, H1, Button, Text, usePortal } from '@meonode/ui' export default function HomePage() { const portal = usePortal() return Center({ children: Column({ children: [ H1('Welcome to MeoNode UI'), Button('Open Modal', { onClick: () => portal.open(MyModal, { name: 'Developer' }) }) ] }) }).render() } // Modal Component const MyModal = ({ data, close }: { data: { name: string }, close: () => void }) => Center({ position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(4px)', children: Column({ backgroundColor: 'theme.base', padding: 'theme.spacing.xl', borderRadius: 'theme.radius.lg', children: [ Text(`Hello ${data.name}!`), Button('Close', { onClick: close }) ] }) }).render()
Boilerplate / Example Repository
Check out the nextjs-meonode repository for a complete, working example of a Next.js project integrated with MeoNode UI. This repository demonstrates best practices, including Context-based theming, Redux Toolkit state management, and React Server Components support.
Best Practices
- Portal System: Always include
PortalHost()within yourThemeProviderchildren to ensure portals inherit the theme context. - StyleRegistry at root layout: Import from
@meonode/ui/nextjs-registryand wrap your app once insrc/app/layout.ts(insideBody, aroundWrapperand all route segments). Do not mount a second registry in nested layouts or insideWrapper. - Styled nested layouts: Use
'use client'shell components for route layouts that render styled MeoNode structure; keep the layout file itself as a server component for metadata. - Theme cookie on server: Pass
themeModefromcookies()intoWrapperso SSR and client hydration pick the same light/dark theme. Avoidprefers-color-schemefallbacks that differ between server and browser. - usePortal Hook: Prefer
usePortalfor all overlays. It manages the portal stack automatically and provides full type safety. - Semantic Tokens: Always use tokens like
theme.primaryinstead of hardcoded colors for automatic light/dark mode support.
Additional Resources
- Live Example: Check out the nextjs-meonode repository to see MeoNode UI in action.
- Core Library: Refer to the official @meonode/ui github for the latest features.
- Release Notes: Check Release Notes for detailed changelog.
Related FAQ
Why am I getting TypeScript build errors with Page components in Next.js?
Most issues come from page export shape mismatches or components returning non-React elements where Next.js expects valid page output. Keep page modules simple and ensure wrapped components return valid rendered output.
How do I use existing JSX components or JSX libraries with MeoNode UI?
Use Node() to wrap third-party JSX components, then pass their props as normal. This keeps interoperability smooth without rewriting external libraries.
More details: /docs/getting-started/faq
On this page
- Next.js Integration Guide for MeoNode UI