@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.
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 npm install @meonode/ui
Configure Emotion's CSS-in-JS functionality. This is optional, as the library will still function without it, but enabling it ensures full support for advanced CSS-in-JS features.
import type { NextConfig } from 'next'; const nextConfig: NextConfig = { // Enables Emotion's CSS-in-JS features, essential for @meonode/ui compiler: { emotion: true, }, // Optimizes module imports to improve build performance experimental: { optimizePackageImports: ['@meonode/ui'], }, turbo: { rules: { '*.svg': { loaders: ['@svgr/webpack'], as: '*.js', }, }, }, }; export default nextConfig;
For robust state management in a large-scale application, Redux Toolkit is an excellent choice. This section shows how to set up your Redux store to manage themes and other global states, ensuring your MeoNode UI components can access them across the application.
src/redux/slice/theme.slice.ts
)This file defines the Redux slice for theme management, including state, actions, and reducers for toggling and setting the theme mode and system theme object.
import { createSlice, type PayloadAction } from '@reduxjs/toolkit' import lightTheme from '@src/constants/themes/lightTheme' import darkTheme from '@src/constants/themes/darkTheme' import Cookies from 'js-cookie' import themeSystem from '@src/constants/themes/themeSystem' export const withThemeSystem = <T extends Record<string, any>>(theme: T): T & typeof themeSystem => ({ ...theme, ...themeSystem }) export interface ThemeState { mode: 'light' | 'dark' system: (typeof lightTheme | typeof darkTheme) & typeof themeSystem } const initialState: ThemeState = { mode: 'light', system: withThemeSystem(lightTheme), } const themeSlice = createSlice({ name: 'theme', initialState, reducers: { setInitialTheme: (state, action: PayloadAction<'light' | 'dark'>) => { state.mode = action.payload state.system = withThemeSystem(action.payload === 'light' ? lightTheme : darkTheme) }, toggleTheme: state => { const newMode = state.mode === 'light' ? 'dark' : 'light' state.mode = newMode state.system = withThemeSystem(newMode === 'light' ? lightTheme : darkTheme) if (typeof window !== 'undefined') { localStorage.setItem('theme', newMode) Cookies.set('theme', newMode, { expires: 365 }) } }, setTheme: (state, action: PayloadAction<'light' | 'dark'>) => { state.mode = action.payload state.system = withThemeSystem(action.payload === 'light' ? lightTheme : darkTheme) if (typeof window !== 'undefined') { localStorage.setItem('theme', action.payload) Cookies.set('theme', action.payload, { expires: 365 }) } }, }, }) export const { toggleTheme, setTheme, setInitialTheme } = themeSlice.actions export default themeSlice.reducer
src/redux/store/store.ts
)This file sets up a singleton Redux store, making it accessible from both the server and client in a Next.js environment. It defines a slice for theme management and creates typed hooks for a better developer experience.
// Import necessary Redux Toolkit and React-Redux utilities import { configureStore, Store, UnknownAction } from '@reduxjs/toolkit' import { Provider, useDispatch, useSelector } from 'react-redux' import { Node, type NodeProps } from '@meonode/ui' import { setupListeners } from '@reduxjs/toolkit/query' import themeSlice, { setInitialTheme, ThemeState } from '@src/redux/slice/theme.slice' // Define the shape of the root state for the Redux store export interface RootState { theme: ThemeState } // Singleton store instance for SSR/CSR compatibility and Portal let globalStore: Store<RootState, UnknownAction> | undefined // Initialize or retrieve the Redux store, optionally with preloaded state export const initializeStore = (preloadedState?: RootState): Store<RootState, UnknownAction> => { if (!globalStore) { globalStore = configureStore({ reducer: { theme: themeSlice, }, preloadedState, }) setupListeners(globalStore.dispatch) } else if (preloadedState) { // Update theme if preloaded state is provided after store creation globalStore.dispatch(setInitialTheme(preloadedState.theme.mode)) } return globalStore } // Type for the app's dispatch function export type AppDispatch = ReturnType<typeof initializeStore>['dispatch'] // Typed hooks for dispatch and selector export const useAppDispatch = useDispatch.withTypes<AppDispatch>() export const useAppSelector = useSelector.withTypes<RootState>() // Wrapper to use Redux Provider with MeoNode's Node API export const ReduxProviderWrapper = ( { store, ...props }: Omit<NodeProps<typeof Provider>, 'store'> & { store: Store } ) => Node(Provider, { ...props, store })
src/components/Providers.ts
)This section provides the implementation of provider components that wrap your Next.js app with Redux and other global providers, ensuring state and theme are available throughout the component tree.
'use client' import { Node, type NodeElement } from '@meonode/ui' import { initializeStore, ReduxProviderWrapper, RootState } from '@src/redux/store' import { lazy, StrictMode, useEffect, useMemo } from 'react' import { setInitialTheme } from '@src/redux/slice/theme.slice' const SnackbarProvider = lazy(() => import('notistack').then(module => ({ default: module.SnackbarProvider }))) export const ProvidersWrapper = ({ reduxPreloadedStore, children }: { reduxPreloadedStore: RootState; children: NodeElement }) => { const initialStore = useMemo(() => initializeStore(reduxPreloadedStore), [reduxPreloadedStore]) useEffect(() => { const initialThemeMode = reduxPreloadedStore.theme.mode initialStore.dispatch(setInitialTheme(initialThemeMode)) const localTheme = localStorage.getItem('theme') as 'light' | 'dark' if (localTheme && localTheme !== initialThemeMode) { initialStore.dispatch(setInitialTheme(localTheme)) } else if (!localTheme) { localStorage.setItem('theme', initialThemeMode) } }, [reduxPreloadedStore]) return Node(StrictMode, { children: ReduxProviderWrapper({ store: initialStore, children: Node(SnackbarProvider, { children }) }), }).render() } export const PortalProviders = Node(StrictMode, { children: ReduxProviderWrapper({ store: initializeStore(), }), })
src/app/layout.ts
)The RootLayout
is a crucial file in Next.js's App Router, as it wraps the entire application. Here, you'll use it to set up global providers, including the Redux store, and to manage initial themes based on user preferences stored in cookies.
import type {Metadata} from 'next' import {Poppins} from 'next/font/google' import './globals.css' import {ReactNode} from 'react' import {Body, Html, Node} from '@meonode/ui' import {ProvidersWrapper} from '@src/components/Providers' import {cookies} from 'next/headers' import darkTheme from '@src/constants/themes/darkTheme' import lightTheme from '@src/constants/themes/lightTheme' import {StyleRegistry} from "@meonode/ui/nextjs-registry"; const poppins = Poppins({ weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'], display: 'swap', subsets: ['latin'], }) export const metadata: Metadata = { title: 'React MeoNode', description: 'Example usage with NextJS', } export default async function RootLayout({children}: Readonly<{ children: ReactNode }>) { const cookieStore = cookies() const initialThemeMode = (cookieStore.get('theme')?.value as 'light' | 'dark') || 'light' return Html({ lang: 'en', children: Body({ className: poppins.className, children: Node(ProvidersWrapper, { reduxPreloadedStore: { theme: { mode: initialThemeMode, colors: initialThemeMode === 'dark' ? darkTheme : lightTheme, }, }, children: StyleRegistry({ children }), }), }), }).render() }
src/app/page.ts
)Within your page components, you can now fully leverage MeoNode UI's powerful, JSX-free syntax. This example demonstrates how to create a visually appealing homepage using Column
, H1
, Button
, and other MeoNode UI primitives, all styled according to your defined theme.
import {Component, Center, Column, H1, Button, Text, Card, Absolute} from '@meonode/ui'; import useTheme from '@src/hooks/useTheme'; import {PortalProviders} from '@src/components/Providers' export default function HomePage() { return Center({ minHeight: '100vh', padding: '2rem', children: Column({ gap: '1.5rem', maxWidth: '800px', textAlign: 'center', children: [ H1('Welcome to MeoNode UI', { fontSize: 'theme.typography.sizes.3xl', color: 'theme.colors.primary', marginBottom: '1rem' }), Text('Build React UIs with type-safe fluency without JSX syntax', { fontSize: 'theme.typography.sizes.lg', color: 'theme.colors.background.content', lineHeight: 1.6, marginBottom: '2rem' }), Button('Explore Components', { backgroundColor: 'theme.colors.primary', color: 'theme.colors.primary.content', padding: '1rem 2rem', borderRadius: '8px', fontSize: 'theme.typography.sizes.lg', cursor: 'pointer', css: { '&:hover': { transform: 'translateY(-2px)', boxShadow: '0 8px 16px rgba(33, 150, 243, 0.3)' } } }), Button('Show Modal', { backgroundColor: 'theme.colors.secondary', color: 'theme.colors.secondary.content', padding: '1rem 2rem', borderRadius: '8px', fontSize: 'theme.typography.sizes.lg', cursor: 'pointer', css: { '&:hover': { transform: 'translateY(-2px)', boxShadow: '0 8px 16px rgba(156, 39, 176, 0.2)' } }, onClick: () => Modal({ name: 'Romeo' }) }) ] }) }).render(); }; // Modal with Portal const Modal = Portal<{ name: string }>(PortalProviders, ({ portal, name }) => { useEffect(() => { setTimeout(portal.unmount, 3000) }, []) return Center({ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.3)', backdropFilter: 'blur(5px)', onClick: e => { if (e.currentTarget === e.target) { portal.unmount() } }, children: [ Column({ boxShadow: '0 2px 10px 2px #000000', backgroundColor: '#fff7ed', borderRadius: 10, paddingInline: 20, children: [Text(`Hello ${name}`, { fontWeight: 600 })], }), ], }) })
For a complete, working example, explore the official react-meonode repository. It serves as an excellent reference for these integration patterns and more.
Repository Details:
useTheme
must be declared with 'use client'
.next/image
and @svgr/webpack
for SVG handling.