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:
- 📖 Understanding Signify - Learn core concepts
- 🚀 Quick Start Guide - Build more examples
- 🏗️ Project Structure - Advanced organization
- 🎯 TypeScript Guide - Advanced TypeScript usage
- 📚 API Reference - Complete API guide
Having issues with Next.js + React Signify? Create an issue on GitHub or check our discussions.