MeoNode UI
  • Getting Started
    • Overview
    • Why Without JSX?
    • Installation
    • Usage
    • Styling
    • Theming
    • Portal System
    • Rules & Patterns
    • Framework Integration
    • FAQ
    • Release Notes
  • MUI Integration
  • Components
  • Hooks

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 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

  1. Portal System: Always include PortalHost() within your ThemeProvider children to ensure portals inherit the theme context.
  2. Next.js Style Registry: Wrap your application with StyleRegistry to ensure CSS-in-JS styles are collected and injected during SSR.
  3. usePortal Hook: Prefer usePortal for all overlays. It manages the portal stack automatically and provides full type safety.
  4. Semantic Tokens: Always use tokens like theme.primary instead of hardcoded colors for automatic light/dark mode support.

Additional Resources

On this page