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
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_")
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_hereInstall 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>
)
}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>
)
}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>
)
}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>
)
}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.