Next.js 14 Integration Guide

Install IssueCapture in Next.js 14

Add bug reporting to your Next.js application in 5 minutes. Works with App Router and Server Components.

Quick Summary

  • Time: 2-5 minutes (7 simple steps)
  • Prerequisites: Next.js 14+, Jira account, IssueCapture account (free)
  • Method: Add Script component to root layout.tsx
  • Works with: App Router, Server Components, Client Components
  • Bundle size: 32KB initial (152KB for screenshot & annotation tools, lazy-loaded)

Prerequisites

  • Next.js 14 or higher installed
  • Jira Cloud account (Software or JSM)
  • IssueCapture account (sign up free)
  • Basic knowledge of Next.js App Router

Step-by-Step Installation

1

Get Your API Key

Sign up for IssueCapture and retrieve your widget API key from the dashboard

  • Go to https://issuecapture.com/signup and create a free account
  • Connect your Jira instance via OAuth
  • Navigate to the Widgets page and create a new widget
  • Copy your API key (starts with "ic_")
2

Add Environment Variable

Store your API key securely in .env.local

  • Create .env.local in your project root (if it doesn't exist)
  • Add NEXT_PUBLIC_ prefix to make it accessible in the browser
  • Never commit this file to version control (add to .gitignore)
  • Restart your dev server after adding environment variables
# .env.local
NEXT_PUBLIC_ISSUECAPTURE_API_KEY=ic_your_api_key_here
3

Install in Root Layout (App Router)

Add IssueCapture to your root layout.tsx for site-wide availability

  • Use Next.js Script component for optimized loading
  • strategy="afterInteractive" loads after page becomes interactive
  • The auto trigger creates a floating button automatically
  • Widget is available on all pages in your app
// app/layout.tsx
import Script from 'next/script'

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

        {/* IssueCapture Widget */}
        <Script
          src="https://issuecapture.com/widget.js"
          strategy="afterInteractive"
          onLoad={() => {
            if (window.IssueCapture) {
              window.IssueCapture.init({
                apiKey: process.env.NEXT_PUBLIC_ISSUECAPTURE_API_KEY!,
                trigger: 'auto', // Creates floating button
                button: {
                  position: 'bottom-right',
                  text: 'Report Bug'
                }
              })
            }
          }}
        />
      </body>
    </html>
  )
}
4

Option: Custom Trigger Button

Use your own button instead of the auto-generated one

  • Replace trigger: "auto" with a CSS selector
  • The selector can target any element on the page
  • Multiple elements can be targeted (e.g., ".report-bug-btn")
  • The widget opens when the element is clicked
// app/layout.tsx
import Script from 'next/script'

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

        {/* Your custom button */}
        <button id="custom-bug-button" className="your-styles">
          Report Issue
        </button>

        {/* IssueCapture Widget */}
        <Script
          src="https://issuecapture.com/widget.js"
          strategy="afterInteractive"
          onLoad={() => {
            if (window.IssueCapture) {
              window.IssueCapture.init({
                apiKey: process.env.NEXT_PUBLIC_ISSUECAPTURE_API_KEY!,
                trigger: '#custom-bug-button' // Use CSS selector
              })
            }
          }}
        />
      </body>
    </html>
  )
}
5

Client Component Example

Open the widget programmatically from a Client Component

  • Use window.IssueCapture.open() to open programmatically
  • Use window.IssueCapture.close() to close programmatically
  • Check if window.IssueCapture exists before calling methods
  • Add TypeScript declarations (see troubleshooting below)
// components/BugReportButton.tsx
'use client'

import { useState } from 'react'

export default function BugReportButton() {
  const [isOpen, setIsOpen] = useState(false)

  const openWidget = () => {
    if (window.IssueCapture) {
      window.IssueCapture.open()
      setIsOpen(true)
    }
  }

  const closeWidget = () => {
    if (window.IssueCapture) {
      window.IssueCapture.close()
      setIsOpen(false)
    }
  }

  return (
    <button
      onClick={openWidget}
      className="px-4 py-2 bg-blue-600 text-white rounded"
    >
      Report Bug
    </button>
  )
}
6

Set User Context (Optional)

Pre-fill user information for authenticated users

  • Pre-fill reporter name and email for authenticated users
  • Component field maps to Jira components (useful for routing)
  • User data is included in the Jira issue description
  • This is optional but improves the developer experience
// app/layout.tsx
import Script from 'next/script'
import { auth } from '@/lib/auth' // Your auth library

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const session = await auth()

  return (
    <html lang="en">
      <body>
        {children}

        <Script
          src="https://issuecapture.com/widget.js"
          strategy="afterInteractive"
          onLoad={() => {
            if (window.IssueCapture) {
              window.IssueCapture.init({
                apiKey: process.env.NEXT_PUBLIC_ISSUECAPTURE_API_KEY!,
                trigger: 'auto',
                user: {
                  name: session?.user?.name || "",
                  email: session?.user?.email || "",
                  component: 'Frontend' // Optional: Jira component
                }
              })
            }
          }}
        />
      </body>
    </html>
  )
}
7

Test the Integration

Verify IssueCapture is working correctly

  • Start your dev server: npm run dev
  • Open your app in the browser
  • You should see the floating bug report button
  • Click it and submit a test issue
  • Check your Jira project for the created issue
  • Open browser console and type: window.IssueCapture.isOpen()

Troubleshooting

TypeScript: Property 'IssueCapture' does not exist on type 'Window'

Add type declarations to your project:

// types/issuecapture.d.ts
interface IssueCaptureAPI {
  init(config: {
    apiKey: string
    trigger?: string | 'auto'
    button?: {
      position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
      text?: string
      icon?: string
    }
    user?: {
      name?: string
      email?: string
      component?: string
    }
    onSubmit?: (data: any) => void
    onOpen?: () => void
    onClose?: () => void
  }): void
  open(): void
  close(): void
  isOpen(): boolean
  destroy(): void
}

interface Window {
  IssueCapture?: IssueCaptureAPI
}

Widget not loading or button not appearing

  • Check browser console for errors
  • Verify API key is correct and starts with "ic_"
  • Ensure NEXT_PUBLIC_ prefix is used in .env.local
  • Restart dev server after changing environment variables
  • Check that your domain is whitelisted in IssueCapture dashboard

CORS errors when submitting issues

Add your domain to the allowed domains list:

  • Go to IssueCapture dashboard → Widgets → Your Widget → Domains
  • Add localhost:3000 for development
  • Add your production domain (e.g., app.example.com)

Hydration mismatch errors

IssueCapture uses Shadow DOM and should not cause hydration issues. If you see errors:

  • Ensure Script component has strategy="afterInteractive"
  • Don't call IssueCapture.init() during server rendering
  • Use onLoad callback to initialize only after script loads

Advanced Configuration

Event Listeners

Listen to widget events:

window.IssueCapture.init({
  apiKey: process.env.NEXT_PUBLIC_ISSUECAPTURE_API_KEY!,
  trigger: 'auto',
  onOpen: () => {
    console.log('Widget opened')
    // Track analytics
  },
  onClose: () => {
    console.log('Widget closed')
  },
  onSubmit: (data) => {
    console.log('Issue submitted:', data)
    // Show success message
  },
  onError: (error) => {
    console.error('Submission failed:', error)
    // Show error message
  }
})

Dynamic Configuration

Change settings based on route or user:

// Different button positions per page
const buttonPosition = pathname.includes('/admin')
  ? 'top-right'
  : 'bottom-right'

window.IssueCapture.init({
  apiKey: process.env.NEXT_PUBLIC_ISSUECAPTURE_API_KEY!,
  trigger: 'auto',
  button: {
    position: buttonPosition
  }
})

Production Best Practices

  • Use environment variables for API keys
  • Whitelist production domains in dashboard
  • Test on staging environment before deploying
  • Monitor bundle size impact (32KB gzipped)
  • Configure CSP headers if using strict Content Security Policy

Ready to get started?

Sign up for a free account and start collecting bug reports in your Next.js app in minutes.