Fix React Màn Hình Trắng (Blank Page): App Không Render Được Mà Không Có Lỗi Console

intermediate⚛️ React2026-03-23| React 16–18, Create React App, Vite, Next.js (static export), mọi trình duyệt, Windows/macOS/Linux

Error Message

Blank white screen with no errors in the browser (React app fails to render)
#react#white-screen#blank-page#build

Những Gì Bạn Đang Thấy

Bạn mở ứng dụng React — ở chế độ dev hoặc bản build production mới — và nhận được một trang trắng hoàn toàn. Không có lỗi. Không có spinner. Không có gì cả. Console của trình duyệt có thể sạch bóng, hoặc có thể chứa một lỗi JavaScript khó hiểu nào đó bị chôn vùi ba tầng sâu trong stack trace đã minified.

Đây là điều thực sự bực bội: React có thể âm thầm không mount mà không ném ra bất kỳ thứ gì rõ ràng. Thẻ <div id="root"> gốc cứ nằm đó, trống rỗng.

Chẩn Đoán Nhanh Trước Tiên

Đừng vội sửa code. Mở DevTools và kiểm tra ba thứ sau:

  • Tab Console — tìm các lỗi JS, kể cả những cảnh báo bạn thường bỏ qua
  • Tab Network — bundle JS của bạn có thực sự tải về với status 200, hay đang trả về 404?
  • Tab Elements — kiểm tra #root; nếu hoàn toàn trống nghĩa là React chưa bao giờ được mount

Những gì bạn tìm thấy ở đây sẽ quyết định cách sửa nào thực sự áp dụng được cho bạn.

Nguyên Nhân Gốc Rễ và Cách Sửa

1. Sai homepage hoặc Đường Dẫn base (Phổ Biến Nhất Trong Production)

Deploy lên một thư mục con như https://example.com/myapp/ nhưng lại build như thể ứng dụng nằm ở root (/)? Trình duyệt tải index.html bình thường, rồi âm thầm trả về 404 cho mọi bundle JS và CSS nó cố tải về. React không bao giờ có cơ hội chạy.

Kiểm tra tab Network: lọc theo JS — bạn sẽ thấy các file .chunk.js trả về 404 thay vì 200.

Sửa cho Create React App — thêm vào package.json:

{
  "homepage": "https://example.com/myapp"
}

Sửa cho Vite — đặt trong vite.config.ts:

export default defineConfig({
  base: '/myapp/',
})

Sửa cho React Router — nếu bạn đang dùng BrowserRouter, cũng cần thêm basename:

import { BrowserRouter } from 'react-router-dom';

<BrowserRouter basename="/myapp">
  <App />
</BrowserRouter>

Build lại và deploy lại. Tab Network giờ sẽ hiển thị tất cả assets trả về 200.

2. Lỗi JavaScript Ném Ra Trong Quá Trình Render (Bị Bắt Âm Thầm)

React 16+ bắt các lỗi render và unmount toàn bộ cây component khi không có Error Boundary nào được đặt. Ở môi trường development bạn thấy một overlay đỏ lớn. Còn ở production? Trang trắng và im lặng hoàn toàn.

Thêm một Error Boundary và cuối cùng bạn sẽ thấy thứ gì đang gây crash:

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    console.error('Render error caught:', error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong: {this.state.error?.message}</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

Bọc nó quanh root của ứng dụng:

// index.tsx or main.tsx
import ErrorBoundary from './ErrorBoundary';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);

Lần sau khi có gì đó crash trong quá trình render, bạn sẽ thấy thông báo lỗi thay vì trang trắng.

3. Sai ID Của Phần Tử Root

Lỗi âm thầm kinh điển. File index.html của bạn có <div id="app"> nhưng file entry lại gọi document.getElementById('root'). React nhận được null, bỏ qua việc mount hoàn toàn, và không hề phàn nàn gì cả.

// getElementById trả về null nếu ID không khớp
const container = document.getElementById('root');

// Ném lỗi sớm thay vì âm thầm không làm gì:
if (!container) {
  throw new Error('Root element #root not found in index.html');
}

ReactDOM.createRoot(container).render(<App />);

Kiểm tra nhanh: mở tab Elements và tìm kiếm id="root" — nó phải khớp chính xác với chuỗi trong JS của bạn, kể cả chữ hoa chữ thường.

4. Biến Môi Trường Bị Thiếu Hoặc Không Hợp Lệ

Giả sử ứng dụng của bạn đọc import.meta.env.VITE_API_URL ở cấp độ module. Nếu biến đó không được định nghĩa trong file .env production của bạn, giá trị sẽ là undefined. Bất kỳ đoạn code nào ngay lập tức cố sử dụng nó — chẳng hạn như tạo base URL cho Axios — sẽ ném lỗi ngay lần render đầu tiên.

Kiểm tra các file .env đã đúng cho từng build target chưa:

# .env.production — for CRA
REACT_APP_API_URL=https://api.example.com

# .env.production — for Vite
VITE_API_URL=https://api.example.com

Ba quy tắc đáng ghi nhớ mãi mãi:

  • Biến CRA phải bắt đầu bằng REACT_APP_ nếu không sẽ vô hình với quá trình build
  • Biến Vite phải bắt đầu bằng VITE_
  • Đừng đọc biến môi trường ở cấp độ đầu module — hãy giữ chúng bên trong component hoặc hàm để có thể thêm kiểm tra null

5. Import Vòng Tròn Hoặc Import Module Thất Bại

Phụ thuộc vòng tròn khiến một module bị resolve thành undefined lúc runtime. Bất kỳ thứ gì cố gọi một hàm từ module đó sẽ crash ngay lần render đầu tiên — âm thầm, trong production.

Trước tiên, kiểm tra xem bản thân quá trình build có báo lỗi không:

# CRA
npm run build 2>&1 | grep -i error

# Vite
npx vite build 2>&1 | grep -i error

Để tìm import vòng tròn trực tiếp, madge là công cụ tốt nhất:

npx madge --circular --extensions ts,tsx src/

Phá vỡ vòng lặp bằng cách chuyển các type hoặc tiện ích dùng chung vào một file riêng biệt nằm ngoài vòng import — không có gì trong vòng lặp import từ file đó.

6. Xung Đột Phiên Bản React

Hai phiên bản React trong một bundle đồng nghĩa với hai hệ thống hook riêng biệt không biết gì về nhau. Hook bị hỏng. Ứng dụng không render. Điều này thường xảy ra khi một thư viện bên thứ ba đóng gói bản React riêng thay vì coi nó là peer dependency.

npm ls react
# or
pnpm why react

Phát hiện hai phiên bản khác nhau? Ghim cả hai trong package.json:

"resolutions": {
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
}

Chạy npm install --force, build lại, và kiểm tra npm ls react lần nữa — bạn sẽ thấy một phiên bản duy nhất trên toàn bộ dependency tree.

Phòng Ngừa

  • Thêm Error Boundary ở root ứng dụng — bắt buộc cho production. Lỗi âm thầm phải trở thành lỗi rõ ràng.
  • Kiểm tra bản production build cục bộ trước khi ship: npm run build && npx serve -s build (CRA) hoặc npx vite preview (Vite)
  • Đặt base/homepage ngay từ đầu nếu bạn biết ứng dụng sẽ nằm trong thư mục con — để đến sau mới thêm thì luôn gây ra bất ngờ
  • Kiểm tra các biến môi trường bắt buộc lúc khởi động để ứng dụng ném lỗi rõ ràng ngay lập tức, thay vì hiển thị màn hình trắng 10 giây sau
  • Bật source map ở môi trường staging — debug lỗi production đã minified mà không có chúng giống như đọc bản đồ không có tên đường

Danh Sách Kiểm Tra Xác Nhận

  • [ ] Tab Network: tất cả bundle JS/CSS trả về 200, không có file nào 404
  • [ ] Tab Elements: #root có các phần tử con sau khi trang tải xong
  • [ ] Console: không có lỗi uncaught hoặc unhandled promise rejection
  • [ ] Error Boundary: hiển thị giao diện fallback thay vì trắng trang khi có gì đó crash
  • [ ] Bản preview production cục bộ đạt: npx serve -s build hoạt động trước khi deploy

Related Error Notes