Demos
Next.js Integration

Next.js Integration

Learn how to integrate Next Dynamic Forms with Next.js applications, featuring full internationalization support with next-intl and server-side rendering compatibility.

What Makes This Different

  • 🌍 Full next-intl Integration - Real translation support, not just dummy adapters
  • 🔄 Server-Side Rendering - Works with Next.js SSR and SSG
  • 🎯 Type-Safe Translations - TypeScript support for translation keys
  • 📱 Next.js Ecosystem - Designed for Next.js apps specifically
  • Production Ready - Optimized for real-world applications

Live Demo

import React, { useState } from 'react';
import { DynamicForm } from '@benyue1978/next-dynamic-forms/core';
import { uiComponents } from './ui-components';
import { formConfig } from './form-config';
import './global.css';

/*
 * Multilingual Form Implementation Example
 * 
 * This demo shows how to directly use DynamicForm to implement multilingual forms.
 * In real projects, you can choose one of the following two approaches:
 * 
 * Approach 1: Using createNextJSAdapter (recommended for Next.js + next-intl projects)
 * ```tsx
 * import { createNextJSAdapter } from '@next-dynamic-forms';
 * import { useTranslations } from 'next-intl';
 * 
 * const NextJSForm = createNextJSAdapter(uiComponents);
 * 
 * export default function ContactPage() {
 *   const t = useTranslations();
 *   
 *   return (
 *     <NextIntlClientProvider messages={messages}>
 *       <NextJSForm
 *         config={formConfig}
 *         currentStepIndex={0}
 *         formData={formData}
 *         onDataChange={handleDataChange}
 *         onNext={handleNext}
 *         isFirstStep={true}
 *         isLastStep={true}
 *         onPrevious={() => {}}
 *       />
 *     </NextIntlClientProvider>
 *   );
 * }
 * ```
 * 
 * Approach 2: Directly using DynamicForm (suitable for any React project)
 * ```tsx
 * import { DynamicForm } from '@next-dynamic-forms/core';
 * 
 * export default function ContactPage() {
 *   const i18nAdapter = {
 *     t: (key, params) => {
 *       // Implement your translation logic
 *       return translatedText;
 *     }
 *   };
 *   
 *   return (
 *     <DynamicForm
 *       config={formConfig}
 *       uiComponents={uiComponents}
 *       i18n={i18nAdapter}
 *       // ... other props
 *     />
 *   );
 * }
 * ```
 */

// Multilingual Messages Library
const messages = {
  en: {
    'form.title': 'Next.js Integration Demo',
    'form.description': 'We would love to hear from you',
    'form.submit': 'Submit',
    'form.next': 'Next',
    'form.previous': 'Previous',
    'form.backToSelection': 'Back to Selection',
    'form.generatePrompt': 'Generate Prompt',
    'ProjectForm.backToSelection': 'Back',
    'ProjectForm.generatePrompt': 'Submit',
    'form.name': 'Full Name',
    'form.name.placeholder': 'Enter your full name',
    'form.email': 'Email Address',
    'form.email.placeholder': 'Enter your email',
    'form.message': 'Message',
    'form.message.placeholder': 'Your message',
    'form.category': 'Category',
    'form.priority': 'Priority',
    'form.tags': 'Tags',
    'form.agree': 'I agree to the terms',
    'form.language': 'Language',
    'form.language.en': 'English',
    'form.language.zh': '中文',
    'form.language.es': 'Español',
    'form.success.title': 'Thank you!',
    'form.success.message': 'We will get back to you soon.',
    'form.success.submitAnother': 'Submit Another'
  },
  zh: {
    'form.title': 'Next.js 集成演示',
    'form.description': '我们期待听到您的声音',
    'form.submit': '提交',
    'form.next': '下一步',
    'form.previous': '上一步',
    'form.backToSelection': '返回选择',
    'form.generatePrompt': '生成提示',
    'ProjectForm.backToSelection': '返回',
    'ProjectForm.generatePrompt': '提交',
    'form.name': '姓名',
    'form.name.placeholder': '请输入您的姓名',
    'form.email': '邮箱地址',
    'form.email.placeholder': '请输入您的邮箱',
    'form.message': '消息',
    'form.message.placeholder': '您的消息',
    'form.category': '分类',
    'form.priority': '优先级',
    'form.tags': '标签',
    'form.agree': '我同意条款',
    'form.language': '语言',
    'form.language.en': 'English',
    'form.language.zh': '中文',
    'form.language.es': 'Español',
    'form.success.title': '提交成功!',
    'form.success.message': '我们会尽快回复您',
    'form.success.submitAnother': '再次提交'
  },
  es: {
    'form.title': 'Demo de Integración Next.js',
    'form.description': 'Nos encantaría saber de ti',
    'form.submit': 'Enviar',
    'form.next': 'Siguiente',
    'form.previous': 'Anterior',
    'form.backToSelection': 'Volver a la selección',
    'form.generatePrompt': 'Generar prompt',
    'ProjectForm.backToSelection': 'Volver',
    'ProjectForm.generatePrompt': 'Enviar',
    'form.name': 'Nombre completo',
    'form.name.placeholder': 'Ingresa tu nombre completo',
    'form.email': 'Correo electrónico',
    'form.email.placeholder': 'Ingresa tu correo',
    'form.message': 'Mensaje',
    'form.message.placeholder': 'Tu mensaje',
    'form.category': 'Categoría',
    'form.priority': 'Prioridad',
    'form.tags': 'Etiquetas',
    'form.agree': 'Acepto los términos',
    'form.language': 'Idioma',
    'form.language.en': 'English',
    'form.language.zh': '中文',
    'form.language.es': 'Español',
    'form.success.title': '¡Gracias!',
    'form.success.message': 'Nos pondremos en contacto contigo pronto',
    'form.success.submitAnother': 'Enviar otro'
  }
};

// Language Selector Component
function LanguageSelector({ currentLocale, onLocaleChange }) {
  return React.createElement('div', { className: 'language-selector' },
    React.createElement('label', { className: 'language-label' }, 'Language:'),
    React.createElement('select', {
      value: currentLocale,
      onChange: (e) => onLocaleChange(e.target.value),
      className: 'language-select'
    },
      React.createElement('option', { value: 'en' }, 'English'),
      React.createElement('option', { value: 'zh' }, '中文'),
      React.createElement('option', { value: 'es' }, 'Español')
    )
  );
}

export default function App() {
  const [formData, setFormData] = useState({});
  const [submitted, setSubmitted] = useState(false);
  const [currentLocale, setCurrentLocale] = useState('en');

  const handleDataChange = (newData) => {
    setFormData(prevData => ({ ...prevData, ...newData }));
  };

  const handleNext = () => {
    console.log('Form submitted:', formData);
    setSubmitted(true);
  };

  // Create i18n adapter - this is the key part
  const i18nAdapter = {
    t: (key, params) => {
      const localeMessages = messages[currentLocale] || messages.en || {};
      let result = localeMessages[key] || key;
      
      if (params) {
        Object.keys(params).forEach(paramKey => {
          result = result.replace(new RegExp('\\{' + paramKey + '\\}', 'g'), params[paramKey]);
        });
      }
      
      return result;
    }
  };

  if (submitted) {
    return React.createElement('div', { className: 'form-container' },
      React.createElement('div', { className: 'success-card' },
        React.createElement('h2', { className: 'success-title' }, i18nAdapter.t('form.success.title')),
        React.createElement('p', { className: 'success-message' }, i18nAdapter.t('form.success.message')),
        React.createElement('button', {
          className: 'btn btn-primary',
          onClick: () => setSubmitted(false)
        }, i18nAdapter.t('form.success.submitAnother'))
      )
    );
  }

  return React.createElement('div', { className: 'form-container' },
    React.createElement('div', { className: 'form-card' },
      React.createElement('div', { className: 'form-header' },
        React.createElement('h1', { className: 'form-title' }, i18nAdapter.t('form.title')),
        React.createElement(LanguageSelector, {
          currentLocale: currentLocale,
          onLocaleChange: setCurrentLocale
        })
      ),
      React.createElement('p', { className: 'form-description' }, i18nAdapter.t('form.description')),
      React.createElement(DynamicForm, {
        config: formConfig,
        currentStepIndex: 0,
        formData: formData,
        onDataChange: handleDataChange,
        onNext: handleNext,
        onPrevious: () => {},
        isFirstStep: true,
        isLastStep: true,
        uiComponents: uiComponents,
        i18n: i18nAdapter
      })
    )
  );
}

⚠️ Demo Environment Limitation: This demo uses an independent translation system instead of createNextJSAdapter because the demo environment doesn't support next-intl dependencies. In real Next.js projects, please use the complete integration method provided below.

Code

import React, { useState } from 'react';
import { DynamicForm } from '@benyue1978/next-dynamic-forms/core';
import { uiComponents } from './ui-components';
import { formConfig } from './form-config';
import './global.css';

/*
 * Multilingual Form Implementation Example
 * 
 * This demo shows how to directly use DynamicForm to implement multilingual forms.
 * In real projects, you can choose one of the following two approaches:
 * 
 * Approach 1: Using createNextJSAdapter (recommended for Next.js + next-intl projects)
 * ```tsx
 * import { createNextJSAdapter } from '@next-dynamic-forms';
 * import { useTranslations } from 'next-intl';
 * 
 * const NextJSForm = createNextJSAdapter(uiComponents);
 * 
 * export default function ContactPage() {
 *   const t = useTranslations();
 *   
 *   return (
 *     <NextIntlClientProvider messages={messages}>
 *       <NextJSForm
 *         config={formConfig}
 *         currentStepIndex={0}
 *         formData={formData}
 *         onDataChange={handleDataChange}
 *         onNext={handleNext}
 *         isFirstStep={true}
 *         isLastStep={true}
 *         onPrevious={() => {}}
 *       />
 *     </NextIntlClientProvider>
 *   );
 * }
 * ```
 * 
 * Approach 2: Directly using DynamicForm (suitable for any React project)
 * ```tsx
 * import { DynamicForm } from '@next-dynamic-forms/core';
 * 
 * export default function ContactPage() {
 *   const i18nAdapter = {
 *     t: (key, params) => {
 *       // Implement your translation logic
 *       return translatedText;
 *     }
 *   };
 *   
 *   return (
 *     <DynamicForm
 *       config={formConfig}
 *       uiComponents={uiComponents}
 *       i18n={i18nAdapter}
 *       // ... other props
 *     />
 *   );
 * }
 * ```
 */

// Multilingual Messages Library
const messages = {
  en: {
    'form.title': 'Next.js Integration Demo',
    'form.description': 'We would love to hear from you',
    'form.submit': 'Submit',
    'form.next': 'Next',
    'form.previous': 'Previous',
    'form.backToSelection': 'Back to Selection',
    'form.generatePrompt': 'Generate Prompt',
    'ProjectForm.backToSelection': 'Back',
    'ProjectForm.generatePrompt': 'Submit',
    'form.name': 'Full Name',
    'form.name.placeholder': 'Enter your full name',
    'form.email': 'Email Address',
    'form.email.placeholder': 'Enter your email',
    'form.message': 'Message',
    'form.message.placeholder': 'Your message',
    'form.category': 'Category',
    'form.priority': 'Priority',
    'form.tags': 'Tags',
    'form.agree': 'I agree to the terms',
    'form.language': 'Language',
    'form.language.en': 'English',
    'form.language.zh': '中文',
    'form.language.es': 'Español',
    'form.success.title': 'Thank you!',
    'form.success.message': 'We will get back to you soon.',
    'form.success.submitAnother': 'Submit Another'
  },
  zh: {
    'form.title': 'Next.js 集成演示',
    'form.description': '我们期待听到您的声音',
    'form.submit': '提交',
    'form.next': '下一步',
    'form.previous': '上一步',
    'form.backToSelection': '返回选择',
    'form.generatePrompt': '生成提示',
    'ProjectForm.backToSelection': '返回',
    'ProjectForm.generatePrompt': '提交',
    'form.name': '姓名',
    'form.name.placeholder': '请输入您的姓名',
    'form.email': '邮箱地址',
    'form.email.placeholder': '请输入您的邮箱',
    'form.message': '消息',
    'form.message.placeholder': '您的消息',
    'form.category': '分类',
    'form.priority': '优先级',
    'form.tags': '标签',
    'form.agree': '我同意条款',
    'form.language': '语言',
    'form.language.en': 'English',
    'form.language.zh': '中文',
    'form.language.es': 'Español',
    'form.success.title': '提交成功!',
    'form.success.message': '我们会尽快回复您',
    'form.success.submitAnother': '再次提交'
  },
  es: {
    'form.title': 'Demo de Integración Next.js',
    'form.description': 'Nos encantaría saber de ti',
    'form.submit': 'Enviar',
    'form.next': 'Siguiente',
    'form.previous': 'Anterior',
    'form.backToSelection': 'Volver a la selección',
    'form.generatePrompt': 'Generar prompt',
    'ProjectForm.backToSelection': 'Volver',
    'ProjectForm.generatePrompt': 'Enviar',
    'form.name': 'Nombre completo',
    'form.name.placeholder': 'Ingresa tu nombre completo',
    'form.email': 'Correo electrónico',
    'form.email.placeholder': 'Ingresa tu correo',
    'form.message': 'Mensaje',
    'form.message.placeholder': 'Tu mensaje',
    'form.category': 'Categoría',
    'form.priority': 'Prioridad',
    'form.tags': 'Etiquetas',
    'form.agree': 'Acepto los términos',
    'form.language': 'Idioma',
    'form.language.en': 'English',
    'form.language.zh': '中文',
    'form.language.es': 'Español',
    'form.success.title': '¡Gracias!',
    'form.success.message': 'Nos pondremos en contacto contigo pronto',
    'form.success.submitAnother': 'Enviar otro'
  }
};

// Language Selector Component
function LanguageSelector({ currentLocale, onLocaleChange }) {
  return React.createElement('div', { className: 'language-selector' },
    React.createElement('label', { className: 'language-label' }, 'Language:'),
    React.createElement('select', {
      value: currentLocale,
      onChange: (e) => onLocaleChange(e.target.value),
      className: 'language-select'
    },
      React.createElement('option', { value: 'en' }, 'English'),
      React.createElement('option', { value: 'zh' }, '中文'),
      React.createElement('option', { value: 'es' }, 'Español')
    )
  );
}

export default function App() {
  const [formData, setFormData] = useState({});
  const [submitted, setSubmitted] = useState(false);
  const [currentLocale, setCurrentLocale] = useState('en');

  const handleDataChange = (newData) => {
    setFormData(prevData => ({ ...prevData, ...newData }));
  };

  const handleNext = () => {
    console.log('Form submitted:', formData);
    setSubmitted(true);
  };

  // Create i18n adapter - this is the key part
  const i18nAdapter = {
    t: (key, params) => {
      const localeMessages = messages[currentLocale] || messages.en || {};
      let result = localeMessages[key] || key;
      
      if (params) {
        Object.keys(params).forEach(paramKey => {
          result = result.replace(new RegExp('\\{' + paramKey + '\\}', 'g'), params[paramKey]);
        });
      }
      
      return result;
    }
  };

  if (submitted) {
    return React.createElement('div', { className: 'form-container' },
      React.createElement('div', { className: 'success-card' },
        React.createElement('h2', { className: 'success-title' }, i18nAdapter.t('form.success.title')),
        React.createElement('p', { className: 'success-message' }, i18nAdapter.t('form.success.message')),
        React.createElement('button', {
          className: 'btn btn-primary',
          onClick: () => setSubmitted(false)
        }, i18nAdapter.t('form.success.submitAnother'))
      )
    );
  }

  return React.createElement('div', { className: 'form-container' },
    React.createElement('div', { className: 'form-card' },
      React.createElement('div', { className: 'form-header' },
        React.createElement('h1', { className: 'form-title' }, i18nAdapter.t('form.title')),
        React.createElement(LanguageSelector, {
          currentLocale: currentLocale,
          onLocaleChange: setCurrentLocale
        })
      ),
      React.createElement('p', { className: 'form-description' }, i18nAdapter.t('form.description')),
      React.createElement(DynamicForm, {
        config: formConfig,
        currentStepIndex: 0,
        formData: formData,
        onDataChange: handleDataChange,
        onNext: handleNext,
        onPrevious: () => {},
        isFirstStep: true,
        isLastStep: true,
        uiComponents: uiComponents,
        i18n: i18nAdapter
      })
    )
  );
}

Language Switching Demo

This demo supports Chinese and English switching, demonstrating the basic implementation of internationalization features. In real projects, you will use the complete internationalization features provided by next-intl.

Next.js Setup

1. Install Dependencies

npm install @benyue1978/next-dynamic-forms next-intl

2. Configure next-intl

Create i18n.ts configuration:

import { createIntl, createIntlCache } from 'next-intl'
 
const cache = createIntlCache()
 
export const getIntl = (locale: string, messages: any) => {
  return createIntl({ locale, messages }, cache)
}

3. Create Translation Files

messages/en.json:

{
  "contact": {
    "title": "Contact Us",
    "description": "We'd love to hear from you",
    "fields": {
      "fullName": "Full Name",
      "email": "Email Address", 
      "message": "Message"
    },
    "placeholders": {
      "fullName": "Enter your full name",
      "email": "Enter your email address",
      "message": "Your message here..."
    },
    "buttons": {
      "submit": "Send Message"
    },
    "success": {
      "title": "Thank you!",
      "message": "We'll get back to you soon."
    }
  }
}

messages/zh.json:

{
  "contact": {
    "title": "联系我们",
    "description": "我们很乐意听到您的声音",
    "fields": {
      "fullName": "姓名",
      "email": "邮箱地址",
      "message": "留言"
    },
    "placeholders": {
      "fullName": "请输入您的姓名",
      "email": "请输入邮箱地址",
      "message": "请输入您的留言..."
    },
    "buttons": {
      "submit": "发送消息"
    },
    "success": {
      "title": "谢谢!",
      "message": "我们会尽快回复您。"
    }
  }
}

Integration Example

1. Import DynamicForm with Next.js Support

import React, { useState } from 'react'
import { DynamicForm } from '@benyue1978/next-dynamic-forms'
import { useTranslations } from 'next-intl'
 
// Define UI components
const uiComponents = {
  Input: ({ value, onChange, ...props }) => (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
      className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
      {...props}
    />
  ),
  Textarea: ({ value, onChange, ...props }) => (
    <textarea
      value={value}
      onChange={(e) => onChange(e.target.value)}
      className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
      rows={4}
      {...props}
    />
  ),
  Label: ({ children }) => (
    <label className="block text-sm font-medium mb-2">{children}</label>
  ),
  Button: ({ children, ...props }) => (
    <button
      className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      {...props}
    >
      {children}
    </button>
  )
}

2. Configure Real Internationalization

Unlike the basic demos that use dummy translation adapters, this example shows how to integrate with actual next-intl translations:

// In your component
export default function ContactForm() {
  const t = useTranslations('contact')
  const [formData, setFormData] = useState({})
  
  // Real next-intl adapter with parameter support
  const i18nAdapter = {
    t: (key: string, params?: Record<string, any>) => {
      // This actually calls next-intl's translation function
      return t(key, params)
    }
  }
 
  const handleDataChange = (newData) => {
    setFormData(prevData => ({ ...prevData, ...newData }))
  }
 
  // Form config uses translation keys that map to your messages
  const formConfig = {
    id: 'contact-form',
    templateName: 'contact',
    steps: [
      {
        id: 'contact',
        title: t('title'), // Resolved from messages/[locale].json
        description: t('description'),
        fields: [
          {
            name: 'fullName',
            type: 'input',
            label: t('fields.fullName'),
            placeholder: t('placeholders.fullName'),
            required: true
          },
          {
            name: 'email',
            type: 'input',
            label: t('fields.email'),
            placeholder: t('placeholders.email'),
            required: true
          },
          {
            name: 'message',
            type: 'textarea',
            label: t('fields.message'),
            placeholder: t('placeholders.message'),
            required: true
          }
        ]
      }
    ]
  }
 
  const handleNext = () => {
    console.log('Form submitted:', formData)
    // Handle form submission with localized success messages
    alert(t('success.message'))
  }
 
  return (
    <DynamicForm
      config={formConfig}
      currentStepIndex={0}
      formData={formData}
      onDataChange={handleDataChange}
      onNext={handleNext}
      uiComponents={uiComponents}
      i18nAdapter={i18nAdapter}
    />
  )
}

Server-Side Rendering

1. Get Server-Side Props

export async function getServerSideProps({ locale }: { locale: string }) {
  const messages = await import(`../messages/${locale}.json`)
  
  return {
    props: {
      messages: messages.default
    }
  }
}

2. Static Generation

export async function getStaticProps({ locale }: { locale: string }) {
  const messages = await import(`../messages/${locale}.json`)
  
  return {
    props: {
      messages: messages.default
    },
    revalidate: 60 // ISR
  }
}

Advanced Features

1. Dynamic Routing

// pages/form/[type].tsx
export default function DynamicForm({ type }) {
  const [formConfig, setFormConfig] = useState(null)
  
  useEffect(() => {
    fetch(`/api/forms/${type}`)
      .then(res => res.json())
      .then(setFormConfig)
  }, [type])
  
  if (!formConfig) return <div>Loading...</div>
  
  return <NextJSForm config={formConfig} />
}

2. API Routes Integration

// pages/api/forms/[type].ts
export default async function handler(req, res) {
  const { type } = req.query
  
  const forms = {
    contact: contactFormConfig,
    registration: registrationFormConfig,
    survey: surveyFormConfig
  }
  
  res.json(forms[type] || contactFormConfig)
}

3. Form Submission

// pages/api/submit.ts
export default async function handler(req, res) {
  if (req.method === 'POST') {
    const formData = req.body
    
    // Process form submission
    await saveToDatabase(formData)
    
    res.json({ success: true })
  }
}

Type Safety

Full TypeScript Support

interface FormField {
  name: string
  type: 'input' | 'textarea' | 'select' | 'checkbox'
  label: string
  required?: boolean
  validation?: ValidationRule[]
}
 
interface FormStep {
  id: string
  title: string
  description: string
  fields: FormField[]
}
 
interface FormConfig {
  id: string
  templateName: string
  steps: FormStep[]
}

Performance Optimizations

1. Code Splitting

const DynamicForm = dynamic(
  () => import('../components/DynamicForm'),
  { loading: () => <div>Loading form...</div> }
)

2. Memoization

const MemoizedForm = React.memo(({ config, i18n }) => {
  return <NextJSForm config={config} i18n={i18n} />
})

Testing

Unit Tests

describe('NextJS Integration', () => {
  it('should render with translations', () => {
    const { getByText } = render(
      <NextIntlProvider locale="en" messages={messages}>
        <ContactForm />
      </NextIntlProvider>
    )
    
    expect(getByText('Contact Us')).toBeInTheDocument()
  })
})

Related Examples