Vercel-style visual feedback widget for Next.js applications
npm install p1-feedbackA Vercel-style visual feedback widget for Next.js applications. Allows visitors to click anywhere on a page to leave comments, which are stored in MongoDB and linked to deployment versions.
- Click anywhere to leave comments with precise page positioning
- Comments stay at exact location on the page (scroll with content)
- Resolve/reopen comments with status filtering (All/Open/Resolved)
- Threaded replies on comments
- Draggable comment modal
- Comments linked to deployment versions (outdated comments are visually marked)
- Optional name/email for commenters
- Shadow DOM isolation (no style conflicts)
- Unique 3-character ID displayed on each pin
- Accent color: #F9A325 (customizable)
- Admin API for managing comments
``bash`
npm install p1-feedback
Create a .env.local file in your Next.js project:
`bashMongoDB connection string
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/feedback
$3
Copy the API routes from the package to your Next.js project:
`
your-project/
└── src/
└── app/
└── api/
├── comments/
│ ├── route.ts
│ └── [id]/
│ └── route.ts
└── admin/
└── comments/
└── route.ts
`You can find these files in the p1-feedback repository.
$3
In your root layout (
app/layout.tsx):`tsx
import { FeedbackProvider } from 'p1-feedback'export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
projectId={process.env.NEXT_PUBLIC_FEEDBACK_PROJECT_ID!}
enabled={process.env.NEXT_PUBLIC_FEEDBACK_ENABLED === 'true'}
deployment={{
id: process.env.VERCEL_DEPLOYMENT_ID,
commitSha: process.env.VERCEL_GIT_COMMIT_SHA,
commitRef: process.env.VERCEL_GIT_COMMIT_REF,
url: process.env.VERCEL_URL,
}}
>
{children}
)
}
`Configuration Options
$3
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
projectId | string | required | Unique identifier for your project |
| enabled | boolean | true | Enable/disable the widget |
| apiUrl | string | '/api' | Base URL for the API endpoints |
| deployment | object | {} | Deployment info for version tracking |
| position | string | 'bottom-right' | Button position: 'bottom-right', 'bottom-left', 'top-right', 'top-left' |$3
`typescript
{
id?: string; // VERCEL_DEPLOYMENT_ID
commitSha?: string; // VERCEL_GIT_COMMIT_SHA
commitRef?: string; // VERCEL_GIT_COMMIT_REF
url?: string; // VERCEL_URL
}
`Usage
1. Click the floating button in the corner to enter comment mode
2. Click anywhere on the page to place a comment (crosshair cursor appears)
3. A pin appears at the clicked location with a draggable comment form
4. Fill in your comment (name and email are optional)
5. Click "Post Comment"
$3
- Filter comments: Use the filter bar (All/Open/Resolved) above the floating button
- Reply to comments: Click a pin, then click "Reply" to add threaded replies
- Resolve comments: Click "Resolve" on any open comment to mark it as resolved
- Drag modal: Grab the comment modal header to move it around the screen
Comments from previous deployments will show an "Old" badge and gray pin.
API Endpoints
$3
| Method | Endpoint | Description |
|--------|----------|-------------|
|
GET | /api/comments?projectId=X&pageUrl=Y | Get comments for a page |
| POST | /api/comments | Create a new comment |
| PATCH | /api/comments/[id] | Update a comment |
| DELETE | /api/comments/[id] | Delete a comment |$3
Requires
X-Admin-Key header matching FEEDBACK_ADMIN_API_KEY.| Method | Endpoint | Description |
|--------|----------|-------------|
|
GET | /api/admin/comments?projectId=X | List all comments (paginated) |
| PATCH | /api/admin/comments | Bulk resolve/delete |
| DELETE | /api/admin/comments?id=X | Delete a comment |Hooks
$3
Access the feedback context from any component:
`tsx
import { useFeedback } from 'p1-feedback'function MyComponent() {
const {
comments,
isLoading,
isCommentMode,
setCommentMode,
refreshComments,
updateComment,
statusFilter,
setStatusFilter,
} = useFeedback()
// Filter by status
const openComments = comments.filter(c => c.status === 'open')
// Resolve a comment
const handleResolve = (id: string) => {
updateComment(id, { status: 'resolved' })
}
return (
{openComments.length} open comments
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as 'all' | 'open' | 'resolved')}
>
)
}
`$3
| Property | Type | Description |
|----------|------|-------------|
|
comments | CommentWithReplies[] | All comments for current page |
| isLoading | boolean | Loading state |
| isCommentMode | boolean | Whether comment mode is active |
| setCommentMode | (mode: boolean) => void | Toggle comment mode |
| refreshComments | () => Promise | Reload comments from server |
| addComment | (input) => Promise | Create a new comment |
| updateComment | (id, input) => Promise | Update comment (e.g., resolve) |
| statusFilter | 'all' \| 'open' \| 'resolved' | Current filter |
| setStatusFilter | (filter) => void | Change status filter |MongoDB Schema
Comments are stored with the following structure:
`typescript
{
_id: ObjectId,
projectId: string,
pageUrl: string,
pageTitle?: string,
position: {
xPercent: number,
yPercent: number,
selector?: string,
selectorOffset?: { x: number, y: number },
viewportWidth: number,
viewportHeight: number,
},
content: string,
authorName?: string,
authorEmail?: string,
parentId?: ObjectId,
threadId: ObjectId,
replyCount: number,
deployment: {
id?: string,
commitSha?: string,
commitRef?: string,
url?: string,
},
status: 'open' | 'resolved',
createdAt: Date,
updatedAt: Date,
}
`$3
For optimal performance, create these indexes:
`javascript
db.comments.createIndex({ projectId: 1, pageUrl: 1, status: 1 })
db.comments.createIndex({ projectId: 1, "deployment.id": 1 })
db.comments.createIndex({ threadId: 1 })
db.comments.createIndex({ createdAt: -1 })
`Cross-Origin Usage
If your API is hosted on a different domain, set the
apiUrl prop:`tsx
projectId="my-project"
apiUrl="https://feedback-api.example.com/api"
>
{children}
``The API includes CORS headers for cross-origin requests.
MIT