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, Store, UnknownAction } from '@reduxjs/toolkit' import { Provider } from 'react-redux' import { createNode } from '@meonode/ui' import appSlice from '@src/redux/slice/app.slice' export interface RootState { app: { isMobile: boolean } } let globalStore: Store<RootState, UnknownAction> | undefined export const initializeStore = (preloadedState?: RootState) => { if (!globalStore) { globalStore = configureStore({ reducer: { app: appSlice }, preloadedState, }) } return globalStore } export const ReduxProviderWrapper = 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.
'use client' import { Node, Theme, ThemeProvider as MeoThemeProvider, PortalProvider, PortalHost, NodeElement } from '@meonode/ui' import { initializeStore, ReduxProviderWrapper, RootState } from '@src/redux/store' import { StrictMode, useEffect, useMemo, useState } from 'react' import { CssBaseline } from '@meonode/mui' import darkTheme from '@src/constants/themes/darkTheme' import lightTheme from '@src/constants/themes/lightTheme' import { StyleRegistry } from '@meonode/ui/nextjs-registry' const ThemeProvider = ({ children, isPortal, theme, }: { children?: NodeElement isPortal?: boolean theme?: Theme }) => { const [loadedTheme, setLoadedTheme] = useState<Theme | undefined>(theme) useEffect(() => { if (!theme) { const stored = localStorage.getItem('theme') // ... initial theme detection logic setLoadedTheme(stored === 'dark' ? darkTheme : lightTheme) } }, [theme]) return MeoThemeProvider({ theme: loadedTheme!, children: [children, PortalHost()], }) } export const Wrapper = ({ preloadedState, initialThemeMode, children, isPortal = false, }: { preloadedState?: RootState initialThemeMode?: Theme['mode'] children?: NodeElement isPortal?: boolean }) => { const initialStore = useMemo(() => initializeStore(preloadedState), [preloadedState]) const theme = initialThemeMode === 'dark' ? darkTheme : lightTheme return Node(StrictMode, { children: StyleRegistry({ children: [ CssBaseline(), ReduxProviderWrapper({ store: initialStore, children: PortalProvider({ children: ThemeProvider({ theme, isPortal, children, }), }), }), ], }), }).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 and wraps the app in StyleRegistry for SSR compatibility.
import { Html, Body, Node } from '@meonode/ui' import { cookies, headers } from 'next/headers' import { StyleRegistry } from '@meonode/ui/nextjs-registry' import { Wrapper } from '@src/components/Wrapper' import { userAgent } from 'next/server' export default async function RootLayout({ children }: { children: React.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 initialThemeMode = cookieStore.get('theme')?.value as 'light' | 'dark' const preloadedState = { app: { isMobile } } return Html({ lang: 'en', 'data-theme': initialThemeMode, className: initialThemeMode === 'dark' ? 'dark-theme' : 'light-theme', children: [ Body({ children: StyleRegistry({ children: Node(Wrapper, { preloadedState, initialThemeMode, children: children as any, }), }), }), ], }).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. - Next.js Style Registry: Wrap your application with
StyleRegistryto ensure CSS-in-JS styles are collected and injected during SSR. - 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.
On this page
- Next.js Integration Guide for MeoNode UI