Next.js App Router Common Pitfalls: 8 Real-World Lessons to Save You Time

Introduction
To be honest, when I first started using Next.js App Router, I got burned pretty badly.
Late last year, our company project needed to upgrade to Next.js 15. I thought, since we’re upgrading anyway, why not migrate from Pages Router to App Router? After all, the official docs made it sound amazing: “Better performance,” “Better developer experience,” “Revolutionary Server Components architecture.” The reality? Day one was a mess of bizarre issues.
Data not updating, pages stuck loading, caching configs not working, confusion between Server and Client Components… The worst part? I spent 3 hours debugging one bug, only to discover I’d forgotten to add 'use client' to the error.tsx file. I wanted to cry.
Later, we did a team survey and found that 80% of the issues people encountered were duplicates. So I compiled these hard-learned lessons hoping to save you some headaches.
This article skips the theory and focuses on real-world experience. For each problem, I’ll tell you: why it happens, how to spot it, and how to fix it. By the end, you’ll know how to avoid App Router’s “gotchas.”
Data Fetching Pitfalls
Pitfall 1: Redundant Client-Side Data Fetching
Scenario:
I needed to display user info on a page, and out of habit, I wrote this code:
// app/profile/page.tsx
'use client'
import { useEffect, useState } from 'react'
export default function ProfilePage() {
const [user, setUser] = useState(null)
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data))
}, [])
if (!user) return <div>Loading...</div>
return <div>Hello, {user.name}</div>
}Looks fine, right? Wrong. This is a classic anti-pattern. Data flows from database → API Route → client, adding an unnecessary network round trip.
Why this happens:
In the Pages Router era, we got used to fetching data client-side with useEffect. But App Router’s Server Components can fetch data directly on the server—no need for that extra API layer.
The right approach:
// app/profile/page.tsx (Server Component by default)
import { db } from '@/lib/db'
export default async function ProfilePage() {
// Fetch directly from database on server
const user = await db.user.findFirst()
return <div>Hello, {user.name}</div>
}Performance improvements are immediate:
- One fewer API request
- Server-to-database latency is typically <10ms (vs 100ms+ client-to-server)
- Smaller client-side JavaScript bundle
Key takeaway:
If you can fetch data in a Server Component, don’t fetch it client-side. Only consider client-side fetching when you need user interaction (search, filters, real-time updates).
Pitfall 2: Default Route Handler Caching
Scenario:
I wrote an API that returns the current time, but no matter how many times I refreshed, the time stayed the same:
// app/api/time/route.ts
export async function GET() {
return Response.json({ time: new Date().toISOString() })
}Refreshed 10 times, same timestamp every time. I thought my code wasn’t running.
Why this happens:
Next.js caches GET Route Handlers by default. Great for static data (like config), terrible for dynamic data.
Solution 1: Explicitly mark as dynamic
// app/api/time/route.ts
export const dynamic = 'force-dynamic' // Force dynamic rendering
export async function GET() {
return Response.json({ time: new Date().toISOString() })
}Solution 2: Use Next.js 15’s new default
Good news: Next.js 15 changed GET Route Handler default behavior to not cache. If you’re still on Next.js 14:
// app/api/time/route.ts
export async function GET() {
return Response.json(
{ time: new Date().toISOString() },
{ headers: { 'Cache-Control': 'no-store' } }
)
}My approach now:
- Static data (config, constants): Explicitly set
export const revalidate = 3600 - Dynamic data (user info, real-time data): Mark
export const dynamic = 'force-dynamic'
Don’t rely on defaults. Make your intent explicit for clearer code.
Pitfall 3: Forgetting to Revalidate After Data Mutations
Scenario:
Built a simple Todo app. After adding a task, the list didn’t update:
// app/todos/page.tsx
export default async function TodosPage() {
const todos = await db.todo.findMany()
return <TodoList todos={todos} />
}
// app/actions.ts
'use server'
export async function addTodo(text: string) {
await db.todo.create({ data: { text } })
// Forgot to revalidate!
}After form submission, the page still showed old data—had to manually refresh to see the new task.
Why this happens:
App Router’s caching is aggressive. Even when data changes, the page won’t auto-update unless you explicitly tell it “this path’s data changed, go refresh.”
The right approach:
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function addTodo(text: string) {
await db.todo.create({ data: { text } })
revalidatePath('/todos') // Revalidate /todos path
}Advanced technique:
If multiple pages show the Todo list (homepage, archive page), use revalidateTag for flexibility:
// app/todos/page.tsx
export default async function TodosPage() {
const todos = await fetch('http://localhost:3000/api/todos', {
next: { tags: ['todos'] } // Tag the data
})
return <TodoList todos={todos} />
}
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function addTodo(text: string) {
await db.todo.create({ data: { text } })
revalidateTag('todos') // Revalidate all data tagged 'todos'
}Key takeaway:
Data mutation trio: Write data →
revalidatePath/revalidateTag→ Redirect (optional)
Server vs Client Components Confusion
Pitfall 4: Using Context in Server Components
Scenario:
I wanted to add global theme switching, so I wrote a ThemeProvider:
// app/providers.tsx
import { createContext } from 'react'
export const ThemeContext = createContext('light')
export function Providers({ children }) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
)
}
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}Got an error: You're importing a component that needs createContext. This only works in a Client Component.
Why this happens:
Server Components don’t support React Context—they render on the server without client-side state management.
The right approach:
Provider must be a Client Component, extracted separately:
// app/providers.tsx
'use client' // Critical: Mark as Client Component
import { createContext, useState } from 'react'
export const ThemeContext = createContext('light')
export function Providers({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
// app/layout.tsx (stays Server Component)
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}My mistake:
Initially I added 'use client' to layout.tsx, turning the entire app into Client Components—losing all Server Components benefits. Remember: Only mark the Provider as Client Component, keep Layout as Server Component.
Pitfall 5: SSR Misconception About Client Components
Scenario:
I used localStorage in a Client Component. Worked fine locally, but deployed it threw: localStorage is not defined.
// app/components/user-info.tsx
'use client'
export default function UserInfo() {
const user = JSON.parse(localStorage.getItem('user') || '{}')
return <div>{user.name}</div>
}Why this happens:
Many think 'use client' means “only runs on client,” but Client Components are still pre-rendered on the server (SSR). localStorage only exists in browsers—accessing it server-side causes errors.
Solution 1: Check environment
'use client'
import { useEffect, useState } from 'react'
export default function UserInfo() {
const [user, setUser] = useState(null)
useEffect(() => {
// useEffect only runs on client
const userData = JSON.parse(localStorage.getItem('user') || '{}')
setUser(userData)
}, [])
if (!user) return null
return <div>{user.name}</div>
}Solution 2: Conditional check
'use client'
export default function UserInfo() {
const user = typeof window !== 'undefined'
? JSON.parse(localStorage.getItem('user') || '{}')
: null
if (!user) return null
return <div>{user.name}</div>
}Key takeaway:
Client Component = component that can have client-side interaction, but it’s still pre-rendered on the server. Browser APIs (localStorage, window, document) must go in
useEffector have environment checks.
Pitfall 6: Overusing ‘use client’
Scenario:
When I first used App Router, I’d add 'use client' whenever I hit an error. Eventually, nearly my entire project was Client Components—Server Components advantages gone.
Why this happens:
Some issues that seem like “needs Client Component” are really just code organization problems.
Anti-pattern:
// app/dashboard/page.tsx
'use client' // Shouldn't be here!
import { useState } from 'react'
export default function Dashboard() {
const [count, setCount] = useState(0)
return (
<div>
<Header /> {/* Static */}
<Stats /> {/* Needs server data */}
<Counter count={count} setCount={setCount} /> {/* Needs interaction */}
</div>
)
}This makes the entire page a Client Component, forcing Stats to fetch client-side.
Correct approach:
// app/dashboard/page.tsx (Server Component)
import { db } from '@/lib/db'
import { Counter } from './counter'
export default async function Dashboard() {
const stats = await db.stats.findFirst() // Server-side data fetch
return (
<div>
<Header /> {/* Server Component */}
<Stats data={stats} /> {/* Server Component */}
<Counter /> {/* Client Component */}
</div>
)
}
// app/dashboard/counter.tsx
'use client' // Only this component is Client Component
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}My criteria for ‘use client’:
- Uses React Hooks (useState, useEffect, useContext, etc.)
- Needs browser event listeners (onClick, onChange, etc.)
- Uses browser APIs (localStorage, window, etc.)
If none of these apply, keep it a Server Component.
Caching Mechanism Pitfalls
Pitfall 7: Client Router Cache Confusion
Scenario:
User edits an article on /posts/1, saves, redirects to /posts list page—but the title in the list is still old. Refreshing the page shows the update.
Why this happens:
App Router has a Client Router Cache that caches pages you’ve visited. Even when data updates, navigating still shows cached old data.
Solution 1: Refresh on navigation
// app/posts/[id]/edit/page.tsx
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function updatePost(id: string, title: string) {
await db.post.update({ where: { id }, data: { title } })
revalidatePath('/posts') // Revalidate list page
revalidatePath(`/posts/${id}`) // Revalidate detail page
redirect('/posts') // Navigate back to list
}Solution 2: Use router.refresh()
'use client'
import { useRouter } from 'next/navigation'
export function EditForm() {
const router = useRouter()
async function handleSubmit() {
await updatePost(...)
router.refresh() // Refresh current route's data
router.push('/posts')
}
}Next.js 15 good news:
Next.js 15 changed Client Router Cache default behavior to not cache—this problem is mostly solved. But if you’re on Next.js 14, handle it manually.
Pitfall 8: revalidate Not Working
Scenario:
I set revalidate = 60 in a Server Component, expecting data to refresh every 60 seconds, but data never changed.
// app/news/page.tsx
export const revalidate = 60 // Expecting regeneration after 60s
export default async function NewsPage() {
const news = await fetch('https://api.example.com/news')
return <NewsList news={news} />
}After deployment, news list didn’t update all day.
Why this happens:
revalidate only works in production—development (npm run dev) doesn’t cache. Also, it only applies to statically generated pages. If the page is identified as dynamic rendering, revalidate is ignored.
Debugging steps:
- Confirm production environment:
npm run build
npm run start- Check if page is static:
During build, look for ○ Static or ● SSG markers. If you see λ Dynamic, the page is dynamic.
- Find what causes dynamic rendering:
Common causes:
- Using
cookies()orheaders() - Using
searchParams(dynamic route params) - Route Handler without explicit
revalidate
Solution:
// app/news/page.tsx
export const revalidate = 60
export default async function NewsPage() {
const news = await fetch('https://api.example.com/news', {
next: { revalidate: 60 } // Fetch-level revalidate
})
return <NewsList news={news} />
}My approach now:
- Pure static content: Use
generateStaticParams+revalidate - Needs dynamic params: Use ISR (Incremental Static Regeneration)
- Real-time data: Mark
dynamic = 'force-dynamic', skip revalidate
Error Handling Pitfalls
Pitfall 9: Forgetting ‘use client’ in error.tsx
Scenario:
I created error.tsx to handle page errors, got: ReactServerComponentsError: Client Component must be used in a Client Component boundary.
// app/error.tsx (wrong)
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
)
}Why this happens:
error.tsx must be a Client Component because it needs React’s Error Boundary mechanism, which only works client-side.
Correct approach:
// app/error.tsx
'use client' // Must have this
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)
}Key takeaway:
Among special files like
error.tsx,loading.tsx,not-found.tsx, onlyerror.tsxmust be a Client Component. Others can be Server Components.
Pitfall 10: redirect in try/catch
Scenario:
I validated forms in a Server Action. After validation failure, I wanted to redirect to an error page, but redirect got caught, blocking navigation.
// app/actions.ts (wrong)
'use server'
import { redirect } from 'next/navigation'
export async function createUser(data: FormData) {
try {
const user = await db.user.create({ data })
redirect(`/users/${user.id}`) // This gets caught!
} catch (error) {
console.error(error)
return { error: 'Failed to create user' }
}
}Why this happens:
redirect() works by throwing a special error that Next.js catches to execute navigation. If you use redirect inside try/catch, your catch block catches it, preventing navigation.
Correct approach:
// app/actions.ts
'use server'
import { redirect } from 'next/navigation'
export async function createUser(data: FormData) {
try {
const user = await db.user.create({ data })
return { success: true, userId: user.id }
} catch (error) {
console.error(error)
return { error: 'Failed to create user' }
}
}
// Call redirect outside try/catch
export async function handleSubmit(data: FormData) {
const result = await createUser(data)
if (result.success) {
redirect(`/users/${result.userId}`) // Outside try/catch
}
}Or this:
'use server'
import { redirect } from 'next/navigation'
export async function createUser(data: FormData) {
try {
const user = await db.user.create({ data })
} catch (error) {
console.error(error)
return { error: 'Failed to create user' }
}
redirect(`/users/${user.id}`) // After try/catch
}Migration Pitfalls
Pitfall 11: 404.js and 500.js Don’t Work
Scenario:
When migrating from Pages Router, I kept pages/404.js and pages/500.js, but they never worked.
Why this happens:
App Router’s error handling changed completely:
404.js→not-found.tsx500.js→error.tsx- Global errors →
global-error.tsx
Correct approach:
// app/not-found.tsx
export default function NotFound() {
return (
<div>
<h2>404 - Page Not Found</h2>
<Link href="/">Go Home</Link>
</div>
)
}
// app/error.tsx
'use client'
export default function Error({ error, reset }) {
return (
<div>
<h2>500 - Server Error</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)
}
// app/global-error.tsx (catches root layout errors)
'use client'
export default function GlobalError({ error, reset }) {
return (
<html>
<body>
<h2>Global Error</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</body>
</html>
)
}Pitfall 12: next-seo Doesn’t Work
Scenario:
My project heavily used next-seo for SEO metadata. After migrating to App Router, it stopped working.
// pages/blog/[slug].tsx (Pages Router era)
import { NextSeo } from 'next-seo'
export default function BlogPost({ post }) {
return (
<>
<NextSeo
title={post.title}
description={post.excerpt}
openGraph={{
title: post.title,
description: post.excerpt,
images: [{ url: post.coverImage }],
}}
/>
<article>{post.content}</article>
</>
)
}Why this happens:
App Router has native generateMetadata API—next-seo is deprecated.
Migration approach:
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
}
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
return <article>{post.content}</article>
}Benefits:
- Type-safe (TypeScript support)
- Supports async/await (can query database directly)
- Better performance (server-rendered)
Performance Optimization Tips
Avoid Unnecessary Client Components
Problem: Entire page becomes Client Component, losing Server Components benefits.
Solution:
Use “leaf node Client Components” strategy:
// ❌ Bad
// app/dashboard/page.tsx
'use client'
export default function Dashboard() {
return (
<div>
<Header />
<Sidebar />
<MainContent />
<Footer />
</div>
)
}
// ✅ Good
// app/dashboard/page.tsx (Server Component)
import { Header } from './header'
import { Sidebar } from './sidebar'
import { MainContent } from './main-content'
import { Footer } from './footer'
export default function Dashboard() {
return (
<div>
<Header /> {/* Server Component */}
<Sidebar /> {/* Client Component (interactive) */}
<MainContent /> {/* Server Component */}
<Footer /> {/* Server Component */}
</div>
)
}
// app/dashboard/sidebar.tsx
'use client' // Only this is Client Component
export function Sidebar() {
const [collapsed, setCollapsed] = useState(false)
return <aside>...</aside>
}Optimize Suspense Boundaries
Problem: Entire page waits for slow data, causing long white screen.
Solution:
Split loading with <Suspense>:
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { FastComponent } from './fast'
import { SlowComponent } from './slow'
export default function Dashboard() {
return (
<div>
{/* Fast data shows immediately */}
<FastComponent />
{/* Slow data shows skeleton */}
<Suspense fallback={<div>Loading...</div>}>
<SlowComponent />
</Suspense>
</div>
)
}Use Parallel Data Fetching
Problem: Serial data fetching—total time = sum of all requests.
Solution:
// ❌ Serial (slow)
export default async function Page() {
const user = await getUser() // 100ms
const posts = await getPosts() // 200ms
const comments = await getComments() // 150ms
// Total: 450ms
}
// ✅ Parallel (fast)
export default async function Page() {
const [user, posts, comments] = await Promise.all([
getUser(),
getPosts(),
getComments(),
])
// Total: 200ms (slowest one)
}Development Environment Pitfalls
Pitfall 13: Hot Reload Connection Leaks
Scenario:
Development server runs for a while, then database error: too many connections.
Why this happens:
Hot Reload re-executes module code. If you create database connections globally, each reload creates new connections without closing old ones.
Solution:
// lib/db.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = global as unknown as { prisma: PrismaClient }
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log: ['query'],
})
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}In development, hot reloads will reuse the same Prisma instance.
Pitfall 14: Dev Server Gets Slower
Scenario:
After running npm run dev for 30 minutes, hot reload becomes super slow, sometimes freezing.
Why this happens:
App Router dev server consumes lots of memory with many pages, especially with many dynamic routes.
Temporary fix:
- Restart dev server (not a real solution)
- Reduce file watching:
// next.config.js
module.exports = {
webpack: (config) => {
config.watchOptions = {
poll: 1000, // Check file changes every second (lower frequency)
aggregateTimeout: 300,
ignored: /node_modules/,
}
return config
},
}Long-term solution:
Upgrade to Next.js 15, use Turbopack:
npm run dev --turboTurbopack’s hot reload is 10x faster, handles large projects without lag.
Summary: Checklist
After all these pitfalls, here’s a quick checklist to avoid 90% of issues:
Data Fetching
- ☑ Use Server Component for data when possible, not Client Component + useEffect
- ☑ Explicitly mark Route Handlers with
dynamic = 'force-dynamic'orrevalidate - ☑ Remember
revalidatePath/revalidateTagafter data mutations
Server/Client Components
- ☑ Provider must be Client Component, but Layout stays Server Component
- ☑ Browser APIs (localStorage, window) must be in
useEffector have environment checks - ☑ Only add
'use client'to truly interactive components, don’t mark entire pages for convenience
Error Handling
- ☑
error.tsxmust have'use client' - ☑ Don’t put
redirectinside try/catch - ☑ Use
global-error.tsxfor root layout errors, noterror.tsx
Caching
- ☑ Upgrade to Next.js 15 for better default caching behavior
- ☑ revalidate only works in production, don’t rely on it in dev
- ☑ For dynamic pages, use
dynamic = 'force-dynamic'instead of revalidate
Migration
- ☑
404.js→not-found.tsx,500.js→error.tsx - ☑
next-seo→generateMetadata - ☑
getServerSideProps→ Server Component direct fetch - ☑
useRouterfromnext/router→next/navigation
Performance
- ☑ Use Suspense to split fast/slow components
- ☑ Parallel data fetching (
Promise.all) - ☑ Database connections use singleton in dev
- ☑ Use Turbopack (
npm run dev --turbo)
Final Thoughts
To be honest, App Router has a learning curve, and hitting these pitfalls is normal at first. But once you master these patterns, development efficiency really improves.
My current approach:
- Think about data flow for new features: Does this data need server rendering or client interaction?
- Check build output when issues arise: Is the page Static or Dynamic? Why?
- Use DevTools effectively: Network panel for requests, Console for error stacks
- Don’t rely on defaults: Make your intent explicit (caching, rendering mode, data revalidation)
Most importantly: Don’t let these pitfalls intimidate you—try it out. Each pitfall teaches you something once you’ve hit it. Next.js App Router’s official docs are detailed—when you hit issues, check the docs first. Answers are usually there.
If this article helped you, share it with friends who are struggling with these issues. Found new pitfalls? Drop them in the comments—I’ll keep updating this list.
Here’s to fewer detours on your App Router journey and more elegant code (instead of bugs)!
FAQ
How do I distinguish Server Component from Client Component?
• Runs on server, not sent to client
• Cannot use useState, useEffect, etc.
• Cannot use browser APIs
Client Component (needs marker):
• Marked with 'use client' directive
• Can use all React hooks
• Can use browser APIs
Rule of thumb: If you need interactivity or browser APIs, use Client Component
Why isn't data updating?
Solutions:
• Set cache: 'no-store' (fetch fresh data on every request)
• Use next: { revalidate: 60 } (revalidate after 60 seconds)
• Use router.refresh() in Client Component to force refresh
Check method: View build output, confirm if page is Dynamic or Static
What if page is stuck loading?
• async Server Component not handling loading state correctly
• Suspense boundary misconfigured
• Data fetching failed but no error handling
Solutions:
• Add loading.tsx file
• Use Suspense to wrap async components
• Add error.tsx to handle errors
Why isn't error.tsx working?
Must add 'use client' directive:
'use client'
export default function Error({ error, reset }) {
return <div>Error: {error.message}</div>
}
Note: error.tsx can only catch child component errors, not its own errors
How do I migrate from Pages Router to App Router?
• getServerSideProps → async Server Component
• getStaticProps → static generation (default)
• next/router → next/navigation
• _app.js → layout.tsx
• _document.js → not needed (handled by layout.tsx)
Recommendation: Start with 1-2 pages as pilot, run through process, then roll out comprehensively
How do I understand the caching mechanism?
• Request Memoization: Same fetch in one request executes only once
• Data Cache: Fetch responses are cached
• Full Route Cache: Entire page is cached (static generation)
• Router Cache: Client-side route cache
Control methods: Use cache: 'no-store', next: { revalidate }, etc.
How do I debug App Router issues?
• View build output (npm run build) to confirm page type
• Use DevTools Network panel to check requests
• Check Console error messages
• View Next.js terminal output
Common issues:
• Page is Static but should be Dynamic → Check fetch cache config
• Data not updating → Check cache and revalidate settings
• Page stuck loading → Check loading.tsx and Suspense
Sources
This article draws insights from the following resources:
- Common mistakes with the Next.js App Router and how to fix them - Vercel
- Next.js App Router: common mistakes and how to fix them
- Don’t Fall into These Mistakes When Migrating from Page Router to App Router in Next.js! | Medium
- Next.js App Router migration: the good, bad, and ugly
- Next.js Official Documentation - App Router
10 min read · Published on: Dec 25, 2025 · Modified on: Jan 22, 2026
Related Posts
Next.js E-commerce in Practice: Complete Guide to Shopping Cart and Stripe Payment Implementation

Next.js E-commerce in Practice: Complete Guide to Shopping Cart and Stripe Payment Implementation
Complete Guide to Next.js File Upload: S3/Qiniu Cloud Presigned URL Direct Upload

Complete Guide to Next.js File Upload: S3/Qiniu Cloud Presigned URL Direct Upload
Next.js Unit Testing Guide: Complete Jest + React Testing Library Setup


Comments
Sign in with GitHub to leave a comment