Sửa lỗi 'useSearchParams() should be wrapped in a suspense boundary' trong Next.js

intermediate⚛️ React2026-05-02| Next.js 13/14/15 (App Router), React 18+, Node.js 18.x trở lên

Error Message

Error: useSearchParams() should be wrapped in a suspense boundary at page "/search". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
#react#nextjs#suspense#usesearchparams

Rào cản khi Build ProductionThanh tìm kiếm của bạn hoạt động hoàn hảo trong môi trường phát triển cục bộ (npm run dev). Nhưng mọi thứ thay đổi khi bạn chạy next build hoặc triển khai lên Vercel. Đột nhiên, quá trình này bị lỗi với thông báo 'CSR bailout' khiến việc triển khai bị dừng lại ngay lập tức.

Error: useSearchParams() should be wrapped in a suspense boundary at page "/search".

Điều này xảy ra vì các tham số tìm kiếm, như ?q=javascript, chỉ tồn tại trong trình duyệt. Vì Next.js cố gắng tạo HTML tĩnh tại thời điểm build, nó gặp phải một "lỗ hổng" nơi dữ liệu đó đáng lẽ phải hiện diện. Nó cần một phương án để xử lý thông tin còn thiếu này trong giai đoạn pre-rendering.

"Lỗ hổng" trong Static HTMLNext.js hướng tới tốc độ bằng cách chuyển các trang của bạn thành các tệp HTML tĩnh trước khi chúng đến tay người dùng. Khi một component gọi useSearchParams(), nó đang yêu cầu thông tin chỉ có ở trình duyệt. Nếu không có Suspense boundary, Next.js sẽ không biết phải làm gì và nó sẽ hủy bỏ (bailout) việc rendering tĩnh hoàn toàn cho component đó.

Việc 'bailout' này thường lan rộng ra. Nó có thể buộc toàn bộ trang của bạn phải phụ thuộc vào client-side rendering, điều này làm giảm điểm số Largest Contentful Paint (LCP) của bạn. Bằng cách sử dụng <Suspense>, bạn đang nói với engine rằng: 'Cứ tiếp tục tạo HTML cho phần còn lại của trang. Tôi sẽ hiển thị một fallback cho phần cụ thể này cho đến khi trình duyệt tiếp quản.'

Cách khắc phục: Tách biệt và Bao bọcĐừng gọi useSearchParams() ở cấp cao nhất của tệp trang (page file). Thay vào đó, hãy chuyển logic động vào một component 'lá'. Điều này giúp kiểm soát sự không chắc chắn và giữ cho phần còn lại của trang luôn ở dạng tĩnh.

1. Tạo một Search Content ComponentTách riêng phần giao diện người dùng thực sự tương tác với URL.

// components/SearchContent.tsx
'use client'

import { useSearchParams } from 'next/navigation'

export default function SearchContent() {
  const searchParams = useSearchParams()
  const query = searchParams.get('q')

  return (
    
      Kết quả cho: {query || 'Tất cả mục'}
    
  )
}

2. Thêm Suspense BoundaryTrong tệp page.tsx chính, hãy bao bọc component mới. Điều này làm hài lòng trình biên dịch và cải thiện trải nghiệm người dùng.

// app/search/page.tsx
import { Suspense } from 'react'
import SearchContent from '@/components/SearchContent'

export default function SearchPage() {
  return (
    
      
        Kết quả tìm kiếm
        
          
        
      
    
  )
}

Lưu ý với LayoutThêm useSearchParams() vào tệp layout.tsx toàn cục là một sai lầm phổ biến. Nó có thể vô tình biến toàn bộ ứng dụng của bạn thành một ứng dụng client-side khổng lồ. Nếu thanh điều hướng của bạn cần URL, hãy chỉ bao bọc ô nhập tìm kiếm hoặc thành phần tương tác cụ thể đó trong Suspense. Điều này giữ cho phần header và thương hiệu xung quanh hoàn toàn ở dạng tĩnh.

Xác minh: Kiểm tra các biểu tượngChạy npm run build và quan sát kỹ đầu ra của terminal. Bạn nên xem Next.js phân loại route /search của bạn như thế nào. Hãy tìm các ký hiệu sau:

  • λ (Lambda): Điều này có nghĩa là trang ở dạng động (dynamic). Đây là kết quả mong đợi khi sử dụng search params.- ○ (Circle): Điều này có nghĩa là trang ở dạng tĩnh (static). Nếu trang tìm kiếm của bạn hiển thị ký hiệu này, hãy đảm bảo Suspense boundary của bạn đang thực sự bắt được hook đó.Cuối cùng, hãy kiểm tra giao diện người dùng. Giới hạn tốc độ mạng (throttle) xuống 'Slow 3G' trong Chrome DevTools. Nếu bạn thấy thông báo 'Đang tải kết quả...' xuất hiện trong tích tắc, việc sửa lỗi đã hoạt động hoàn hảo.

Related Error Notes