Next.js Installation
Learn how to install the Notedis feedback widget in your Next.js application.
Prerequisites
- Next.js 13+ application (works with Pages Router or App Router)
- Notedis account with site key
- Basic knowledge of Next.js
Compatibility:
- ✅ Next.js 13+ (App Router)
- ✅ Next.js 12+ (Pages Router)
- ✅ Server-side rendering (SSR)
- ✅ Static site generation (SSG)
- ✅ Client-side rendering (CSR)
Installation Methods
Choose the method that fits your Next.js setup:
- Script Tag in Document - Recommended for most projects
- useEffect Hook - For component-level control
- Custom _document.js - For Pages Router global setup
- Script Component - Using Next.js Script component
Method 1: Script Tag in Document (Recommended)
Step 1: Get Your Site Key
- Log in to your Notedis dashboard
- Navigate to Sites
- Click the Edit button on your site (or create a new one)
- Copy your Site Key (starts with
site_)
Step 2: Using App Router (Next.js 13+)
Create or modify app/layout.tsx (or layout.js):
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Next.js App',
description: 'My awesome Next.js application',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
{/* Notedis Feedback Widget */}
<script
dangerouslySetInnerHTML={{
__html: `
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
};
`,
}}
/>
<script src="https://notedis.test/js/widget.js" defer />
</body>
</html>
)
}
Replace YOUR_SITE_KEY_HERE with your actual site key.
Step 3: Using Pages Router (Next.js 12)
Create or modify pages/_document.tsx (or _document.js):
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
{/* Notedis Feedback Widget */}
<script
dangerouslySetInnerHTML={{
__html: `
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
};
`,
}}
/>
<script src="https://notedis.test/js/widget.js" defer />
</body>
</Html>
)
}
Replace YOUR_SITE_KEY_HERE with your actual site key.
Method 2: useEffect Hook
For component-level control or conditional loading:
App Router (Next.js 13+)
Create a client component components/NotedisWidget.tsx:
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
// Configure Notedis
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
// Load widget script
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
// Cleanup function
return () => {
// Remove script if component unmounts
document.head.removeChild(script)
}
}, [])
return null // This component doesn't render anything
}
Add to your layout app/layout.tsx:
import NotedisWidget from '@/components/NotedisWidget'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<NotedisWidget />
</body>
</html>
)
}
Pages Router (Next.js 12)
Create components/NotedisWidget.tsx:
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
// Configure Notedis
;(window as any).notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
// Load widget script
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [])
return null
}
Add to pages/_app.tsx:
import type { AppProps } from 'next/app'
import NotedisWidget from '@/components/NotedisWidget'
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<NotedisWidget />
</>
)
}
Method 3: Next.js Script Component
Using the built-in Next.js Script component:
App Router
In app/layout.tsx:
import Script from 'next/script'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
{/* Configure Notedis */}
<Script
id="notedis-config"
strategy="lazyOnload"
dangerouslySetInnerHTML={{
__html: `
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
};
`,
}}
/>
{/* Load Notedis widget */}
<Script
src="https://notedis.test/js/widget.js"
strategy="lazyOnload"
/>
</body>
</html>
)
}
Pages Router
In pages/_app.tsx:
import Script from 'next/script'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
{/* Configure Notedis */}
<Script
id="notedis-config"
strategy="lazyOnload"
dangerouslySetInnerHTML={{
__html: `
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
};
`,
}}
/>
{/* Load Notedis widget */}
<Script
src="https://notedis.test/js/widget.js"
strategy="lazyOnload"
/>
</>
)
}
Configuration Options
Customize the widget by modifying the notedisConfig object:
Position
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right', // bottom-right, bottom-left, top-right, top-left
color: '#3B82F6'
}
Color
Match your brand colors:
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#FF6B35' // Any hex color
}
See Configuration for all available options.
TypeScript Support
Type Definitions
Create types/notedis.d.ts:
interface NotedisConfig {
siteKey: string
apiUrl: string
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
color?: string
}
interface Window {
notedisConfig: NotedisConfig
}
Usage with Types
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
// TypeScript now knows about notedisConfig
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [])
return null
}
Environment Variables
Store your site key in environment variables:
Create .env.local
NEXT_PUBLIC_NOTEDIS_SITE_KEY=your_site_key_here
Use in Component
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
window.notedisConfig = {
siteKey: process.env.NEXT_PUBLIC_NOTEDIS_SITE_KEY!,
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [])
return null
}
Important: Use NEXT_PUBLIC_ prefix for client-side environment variables.
Conditional Loading
Load Only in Production
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
// Only load in production
if (process.env.NODE_ENV !== 'production') {
return
}
window.notedisConfig = {
siteKey: process.env.NEXT_PUBLIC_NOTEDIS_SITE_KEY!,
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [])
return null
}
Load on Specific Routes
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export default function NotedisWidget() {
const pathname = usePathname()
useEffect(() => {
// Don't load on admin pages
if (pathname.startsWith('/admin')) {
return
}
window.notedisConfig = {
siteKey: process.env.NEXT_PUBLIC_NOTEDIS_SITE_KEY!,
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [pathname])
return null
}
Next.js-Specific Considerations
Server-Side Rendering (SSR)
The widget is client-side only:
- Use
'use client'directive (App Router) - Load in
useEffectto avoid SSR issues - Check for
windowbefore accessing
Example with SSR check:
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
// Only runs on client
if (typeof window === 'undefined') {
return
}
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [])
return null
}
Static Site Generation (SSG)
The widget works perfectly with SSG:
- Widget loads on client after hydration
- No configuration needed
- Works with
getStaticPropsandgetStaticPaths
Client-Side Navigation
The widget persists across Next.js client-side navigation:
- Loads once on initial page load
- Persists when navigating with
<Link> - No re-initialization needed
Turbopack Compatibility
For Next.js 13+ with Turbopack:
The widget is fully compatible with Turbopack:
- No special configuration required
- Works in dev mode with
next dev --turbo - Works in production builds
Verification
Test the Installation
- Start your Next.js dev server:
npm run dev
# or
yarn dev
# or
pnpm dev
- Open http://localhost:3000
- Look for the feedback button in bottom-right corner
- Click to test the widget
- Submit test feedback
- Check your Notedis dashboard
Production Testing
- Build your application:
npm run build
npm start
- Test on the production build
- Verify widget appears and functions correctly
Troubleshooting
Widget Not Appearing
Check These:
- Client Component Directive
- Ensure
'use client'at top of component (App Router) - Widget needs to run on client side
- Ensure
- Environment Variable
- Verify
NEXT_PUBLIC_NOTEDIS_SITE_KEYis set - Restart dev server after adding env vars
- Check
.env.localis not in.gitignore
- Verify
- Browser Console
- Open DevTools (F12)
- Check Console tab for errors
- Look for "Notedis: Missing configuration"
- Script Loading
- Check Network tab in DevTools
- Verify widget.js loads successfully
- Check for 404 or CORS errors
TypeScript Errors
Issue: Property 'notedisConfig' does not exist on type 'Window'
Solution: Add type definitions (see TypeScript Support section above)
SSR Errors
Issue: ReferenceError: window is not defined
Solution:
- Use
'use client'directive - Load widget in
useEffect - Add
typeof window !== 'undefined'check
Route Changes Not Working
Issue: Widget doesn't capture new route after navigation.
Solution: This is actually correct behavior - the widget automatically detects URL changes. No action needed.
Advanced Usage
Custom Trigger Button
Hide default button and use your own:
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
window.notedisConfig = {
siteKey: 'YOUR_SITE_KEY_HERE',
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [])
return (
<button
onClick={() => {
// Trigger widget programmatically
if (window.Notedis) {
window.Notedis.open()
}
}}
className="fixed bottom-4 right-4 bg-blue-600 text-white px-4 py-2 rounded-lg"
>
Send Feedback
</button>
)
}
Multiple Environments
Different site keys for different environments:
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
const getSiteKey = () => {
if (process.env.NODE_ENV === 'production') {
return process.env.NEXT_PUBLIC_NOTEDIS_SITE_KEY_PROD!
}
return process.env.NEXT_PUBLIC_NOTEDIS_SITE_KEY_DEV!
}
window.notedisConfig = {
siteKey: getSiteKey(),
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
document.head.removeChild(script)
}
}, [])
return null
}
Performance Optimization
Script Loading Strategy
Recommended: Use lazyOnload strategy with Next.js Script component:
<Script
src="https://notedis.test/js/widget.js"
strategy="lazyOnload"
/>
Strategies:
lazyOnload- Load after page is idle (recommended)afterInteractive- Load after page is interactivebeforeInteractive- Load before page is interactive (not recommended)
Code Splitting
The widget automatically benefits from Next.js code splitting:
- Doesn't increase main bundle size
- Loads separately from your application code
- No impact on First Contentful Paint (FCP)
Deployment
Vercel
The widget works perfectly on Vercel:
- Add environment variable in Vercel dashboard
- Go to Settings → Environment Variables
- Add
NEXT_PUBLIC_NOTEDIS_SITE_KEY - Redeploy your application
Netlify
Works on Netlify:
- Add environment variable in Netlify UI
- Site settings → Build & deploy → Environment
- Add
NEXT_PUBLIC_NOTEDIS_SITE_KEY - Trigger new deploy
Self-Hosted
Works on any hosting platform:
- Set environment variables in your hosting platform
- Ensure JavaScript is enabled
- No special configuration needed
Examples
Complete App Router Example
// app/layout.tsx
import { Metadata } from 'next'
import NotedisWidget from '@/components/NotedisWidget'
import './globals.css'
export const metadata: Metadata = {
title: 'My Next.js App',
description: 'Built with Next.js 13+',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<NotedisWidget />
</body>
</html>
)
}
// components/NotedisWidget.tsx
'use client'
import { useEffect } from 'react'
export default function NotedisWidget() {
useEffect(() => {
if (typeof window === 'undefined') return
window.notedisConfig = {
siteKey: process.env.NEXT_PUBLIC_NOTEDIS_SITE_KEY!,
apiUrl: 'https://notedis.test',
position: 'bottom-right',
color: '#3B82F6'
}
const script = document.createElement('script')
script.src = 'https://notedis.test/js/widget.js'
script.defer = true
document.head.appendChild(script)
return () => {
if (document.head.contains(script)) {
document.head.removeChild(script)
}
}
}, [])
return null
}
// types/notedis.d.ts
interface NotedisConfig {
siteKey: string
apiUrl: string
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
color?: string
}
interface Window {
notedisConfig: NotedisConfig
Notedis?: {
open: () => void
close: () => void
}
}
# .env.local
NEXT_PUBLIC_NOTEDIS_SITE_KEY=site_your_key_here
Support
Getting Help
Documentation:
- Configuration - Widget configuration
- Customization - Advanced options
- Troubleshooting - Common issues
- FAQ - Frequently asked questions
Next.js-Specific Support:
- Email: [email protected]
- Include Next.js version
- Specify App Router or Pages Router
- Share relevant code snippets
- Include error messages from console
Next Steps
- Configuration - Customize appearance
- Customization - Advanced features
- Features - Explore all features
- Dashboard Guide - Manage feedback