Skip to content

Work with NextJS

This guide covers setting up React Signify with Next.js, including both Pages Router and App Router configurations, SSR considerations, and optimization techniques.

Prerequisites

  • Node.js >= 18.0.0 (for Next.js 13+)
  • NPM, Yarn, pnpm, or Bun package manager

Project Setup

1. Create New Next.js Project

Using NPM

bash
# JavaScript
npx create-next-app@latest my-signify-app

# TypeScript (Recommended)
npx create-next-app@latest my-signify-app --typescript

cd my-signify-app
npm install

Using Yarn

bash
# JavaScript
yarn create next-app my-signify-app

# TypeScript (Recommended)
yarn create next-app my-signify-app --typescript

cd my-signify-app
yarn install

Using pnpm

bash
# JavaScript
pnpm create next-app my-signify-app

# TypeScript (Recommended)
pnpm create next-app my-signify-app --typescript

cd my-signify-app
pnpm install

Using Bun

bash
# JavaScript
bun create next-app my-signify-app

# TypeScript (Recommended)
bun create next-app my-signify-app --typescript

cd my-signify-app
bun install

2. Install React Signify

bash
# NPM
npm install react-signify

# Yarn
yarn add react-signify

# pnpm
pnpm add react-signify

# Bun
bun add react-signify

Next.js Configuration

Basic Configuration

Update your next.config.js for optimal React Signify performance:

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ['react-signify'], // Optional: for better tree-shaking
  experimental: {
    optimizePackageImports: ['react-signify'],
  },
  // Enable SWC minification for better performance
  swcMinify: true,
};

module.exports = nextConfig;

Advanced Configuration

For larger applications with optimization:

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ['react-signify'],
  experimental: {
    optimizePackageImports: ['react-signify'],
    turbo: {
      rules: {
        '*.svg': {
          loaders: ['@svgr/webpack'],
          as: '*.js',
        },
      },
    },
  },
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.optimization.splitChunks.chunks = 'all';
      config.optimization.splitChunks.cacheGroups = {
        ...config.optimization.splitChunks.cacheGroups,
        'react-signify': {
          name: 'react-signify',
          test: /[\\/]node_modules[\\/]react-signify[\\/]/,
          chunks: 'all',
          priority: 10,
        },
      };
    }
    return config;
  },
};

module.exports = nextConfig;

TypeScript Configuration

tsconfig.json Setup

Ensure your tsconfig.json is optimized for Next.js + React Signify:

json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "es6"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/store/*": ["./src/store/*"],
      "@/types/*": ["./src/types/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Project Structure

Files You'll Create (App Router)

src/
├── app/
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── components/
│   ├── Counter.tsx
│   └── CounterButton.tsx
└── store/
    └── counter.ts

Files You'll Create (Pages Router)

src/
├── pages/
│   ├── _app.tsx
│   └── index.tsx
├── components/
│   ├── Counter.tsx
│   └── CounterButton.tsx
├── store/
│   └── counter.ts
└── styles/
    └── globals.css

React Signify Integration

1. Create Store File

Create a store file to manage your counter state:

tsx
// src/store/counter.ts
import { signify } from 'react-signify';

export const sCounter = signify(0);

App Router Setup (Next.js 13+)

2. Create Main Component (Display with use)

Create the main component that displays the counter value:

tsx
// src/components/Counter.tsx
'use client';

import React from 'react';
import { sCounter } from '@/store/counter';
import CounterButton from './CounterButton';

export default function Counter() {
  const count = sCounter.use(); // Use to display value

  return (
    <div>
      <h2>Counter: {count}</h2>
      <CounterButton />
    </div>
  );
}

3. Create Child Component (Update with set)

Create a child component that updates the counter:

tsx
// src/components/CounterButton.tsx
'use client';

import React from 'react';
import { sCounter } from '@/store/counter';

export default function CounterButton() {
  const handleIncrement = () => {
    sCounter.set(prev => {
      prev.value += 1; // Set to update value directly
    });
  };

  return (
    <button onClick={handleIncrement}>
      +1
    </button>
  );
}

4. Root Layout

tsx
// src/app/layout.tsx
import React from 'react';
import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
  title: 'Next.js + React Signify',
  description: 'A demo app showcasing Next.js with React Signify',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
      </body>
    </html>
  );
}

5. Home Page

tsx
// src/app/page.tsx
import React from 'react';
import Counter from '@/components/Counter';

export default function HomePage() {
  return (
    <main>
      <h1>Next.js + React Signify Demo</h1>
      <Counter />
    </main>
  );
}

Pages Router Setup (Next.js 12 and below)

1. App Wrapper

tsx
// src/pages/_app.tsx
import React from 'react';
import type { AppProps } from 'next/app';
import '@/styles/globals.css';

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

2. Home Page

tsx
// src/pages/index.tsx
import React from 'react';
import type { NextPage } from 'next';
import Head from 'next/head';
import Counter from '@/components/Counter';

const HomePage: NextPage = () => {
  return (
    <>
      <Head>
        <title>Next.js + React Signify</title>
        <meta name="description" content="Demo app with Next.js and React Signify" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <h1>Next.js + React Signify Demo</h1>
        <Counter />
      </main>
    </>
  );
};

export default HomePage;

Server-Side Rendering (SSR) Considerations

Hydration Handling

tsx
// src/components/ClientOnly.tsx
'use client';

import React, { useEffect, useState } from 'react';

interface ClientOnlyProps {
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

export default function ClientOnly({ children, fallback = null }: ClientOnlyProps) {
  const [hasMounted, setHasMounted] = useState(false);

  useEffect(() => {
    setHasMounted(true);
  }, []);

  if (!hasMounted) {
    return <>{fallback}</>;
  }

  return <>{children}</>;
}

SSR-Safe Store Initialization

tsx
// src/hooks/useHydration.ts
'use client';

import { useEffect, useState } from 'react';

export function useHydration() {
  const [isHydrated, setIsHydrated] = useState(false);

  useEffect(() => {
    setIsHydrated(true);
  }, []);

  return isHydrated;
}

Performance Optimization

Code Splitting

tsx
// src/components/LazyCounter.tsx
import dynamic from 'next/dynamic';

const Counter = dynamic(() => import('./Counter'), {
  loading: () => <div>Loading counter...</div>,
  ssr: false, // Disable SSR for this component if needed
});

export default Counter;

Bundle Analysis

bash
# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer

# Update next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer(nextConfig);

# Analyze bundle
ANALYZE=true npm run build

Deployment

Vercel Deployment

bash
# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

# Or connect your Git repository to Vercel

Custom Server (if needed)

javascript
// server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = 3000;

const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  createServer(async (req, res) => {
    try {
      const parsedUrl = parse(req.url, true);
      await handle(req, res, parsedUrl);
    } catch (err) {
      console.error('Error occurred handling', req.url, err);
      res.statusCode = 500;
      res.end('internal server error');
    }
  }).listen(port, (err) => {
    if (err) throw err;
    console.log(`> Ready on http://${hostname}:${port}`);
  });
});

Troubleshooting

Common Issues

1. Hydration Mismatch

tsx
// Use ClientOnly component for client-side only content
import ClientOnly from '@/components/ClientOnly';

export default function MyComponent() {
  return (
    <ClientOnly fallback={<div>Loading...</div>}>
      <ComponentThatUsesSignify />
    </ClientOnly>
  );
}

2. "Cannot resolve module 'react-signify'"

bash
# Clear Next.js cache
rm -rf .next
npm install

3. TypeScript errors

bash
# Ensure Next.js types are installed
npm install --save-dev @types/node

# Rebuild types
npm run build

4. Performance issues

javascript
// next.config.js - Optimize for production
const nextConfig = {
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production',
  },
  experimental: {
    optimizeCss: true,
  },
};

Testing

Jest + Testing Library Setup

bash
npm install --save-dev jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom
javascript
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
};
javascript
// jest.setup.js
import '@testing-library/jest-dom';

Example Test

tsx
// src/components/__tests__/Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from '../Counter';
import { sCounter } from '@/store/counter';

describe('Counter', () => {
  beforeEach(() => {
    sCounter.reset();
  });

  it('renders counter with initial value', () => {
    render(<Counter />);
    expect(screen.getByText('0')).toBeInTheDocument();
  });

  it('increments counter when + button is clicked', () => {
    render(<Counter />);
    const incrementButton = screen.getByText('+');
    fireEvent.click(incrementButton);
    expect(screen.getByText('1')).toBeInTheDocument();
  });
});

Next Steps

After successful setup:

  1. 📖 Understanding Signify - Learn core concepts
  2. 🚀 Quick Start Guide - Build more examples
  3. 🏗️ Project Structure - Advanced organization
  4. 🎯 TypeScript Guide - Advanced TypeScript usage
  5. 📚 API Reference - Complete API guide

Having issues with Next.js + React Signify? Create an issue on GitHub or check our discussions.

Released under the MIT License.