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:

  1. Script Tag in Document - Recommended for most projects
  2. useEffect Hook - For component-level control
  3. Custom _document.js - For Pages Router global setup
  4. Script Component - Using Next.js Script component

Method 1: Script Tag in Document (Recommended)

Step 1: Get Your Site Key

  1. Log in to your Notedis dashboard
  2. Navigate to Sites
  3. Click the Edit button on your site (or create a new one)
  4. 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 useEffect to avoid SSR issues
  • Check for window before 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 getStaticProps and getStaticPaths

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

  1. Start your Next.js dev server:
npm run dev
# or
yarn dev
# or
pnpm dev
  1. Open http://localhost:3000
  2. Look for the feedback button in bottom-right corner
  3. Click to test the widget
  4. Submit test feedback
  5. Check your Notedis dashboard

Production Testing

  1. Build your application:
npm run build
npm start
  1. Test on the production build
  2. Verify widget appears and functions correctly

Troubleshooting

Widget Not Appearing

Check These:

  1. Client Component Directive
    • Ensure 'use client' at top of component (App Router)
    • Widget needs to run on client side
  2. Environment Variable
    • Verify NEXT_PUBLIC_NOTEDIS_SITE_KEY is set
    • Restart dev server after adding env vars
    • Check .env.local is not in .gitignore
  3. Browser Console
    • Open DevTools (F12)
    • Check Console tab for errors
    • Look for "Notedis: Missing configuration"
  4. 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 interactive
  • beforeInteractive - 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:

  1. Add environment variable in Vercel dashboard
  2. Go to Settings → Environment Variables
  3. Add NEXT_PUBLIC_NOTEDIS_SITE_KEY
  4. Redeploy your application

Netlify

Works on Netlify:

  1. Add environment variable in Netlify UI
  2. Site settings → Build & deploy → Environment
  3. Add NEXT_PUBLIC_NOTEDIS_SITE_KEY
  4. 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:

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