> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hadoseo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Race Conditions Causing Bad Pre-renders

> Fix issues where pre-rendered pages show 404s, blank content, or loading states

Sometimes Hado SEO pre-renders your page before content has loaded, resulting in 404 pages, blank content, or loading states being cached.

## Symptoms

* Google shows your 404 page in search results
* Pre-rendered HTML contains "Loading..." or spinner
* Content is blank or incomplete

## How the Renderer Decides Your Page Is Ready

The renderer loads your page in a headless browser and runs through these checks:

1. **Network idle** — waits up to 5 seconds for all network requests to finish
2. **Loading indicator check** — looks for visible spinners or loading text
3. **Content thresholds** — requires at least **100 characters** of visible text and **20 DOM elements**

If your page doesn't pass these checks within **12 seconds**, the renderer captures whatever is on screen at that point — which may be a loading state, an error, or blank content.

### What counts as a loading indicator?

The renderer detects these CSS selectors:

```css theme={null}
.loading, .spinner, [data-loading], .skeleton, [aria-busy="true"]
```

It also detects loading text like "Loading...", "Please wait", "Cargando", "Chargement", and equivalents in 20+ languages — but only when the page has less than 200 characters of visible text.

<Info>
  If your app uses **none of these indicators** while fetching data, the renderer has no way to know it should keep waiting. It may capture a half-rendered page that technically passes the content thresholds.
</Info>

## Common Causes and Fixes

<AccordionGroup>
  <Accordion title="No loading indicators while fetching data" icon="eye-slash">
    **Cause:** Your app fetches data after mount but doesn't show any loading state. The renderer sees the initial shell (which may have enough DOM elements and text to pass thresholds) and captures it before data arrives.

    **Fix:** Add a recognized loading indicator while data is being fetched:

    ```jsx theme={null}
    // Bad - renders partial content with no loading signal
    function ProductPage() {
      const [product, setProduct] = useState(null);
      useEffect(() => { fetchProduct().then(setProduct); }, []);
      return <div>{product ? <ProductDetails /> : <EmptyShell />}</div>;
    }

    // Good - shows a loading indicator the renderer will detect
    function ProductPage() {
      const [product, setProduct] = useState(null);
      useEffect(() => { fetchProduct().then(setProduct); }, []);
      if (!product) return <div className="loading">Loading...</div>;
      return <ProductDetails product={product} />;
    }
    ```

    Use any of these recognized patterns: `.loading`, `.spinner`, `.skeleton`, `[data-loading]`, or `[aria-busy="true"]`.
  </Accordion>

  <Accordion title="Custom loading indicators not detected" icon="spinner">
    **Cause:** Your app uses custom class names like `.is-loading`, `.page-spinner`, or `[data-state="loading"]` that the renderer doesn't recognize.

    **Fix:** Switch to one of the recognized selectors, or add a recognized class alongside your custom one:

    ```jsx theme={null}
    // Bad - custom class not detected
    <div className="page-spinner">Loading...</div>

    // Good - add a recognized class
    <div className="page-spinner loading">Loading...</div>

    // Also good - use aria-busy
    <div className="page-spinner" aria-busy="true">Loading...</div>
    ```
  </Accordion>

  <Accordion title="Content takes too long to render" icon="clock">
    **Cause:** Your page takes longer than 12 seconds to render meaningful content — usually due to slow API calls, large data fetches, or heavy client-side computation.

    **Fix:** Optimize data loading for your critical content path:

    * Fetch only the data needed for initial render
    * Defer non-essential data (comments, recommendations) to load after primary content
  </Accordion>

  <Accordion title="404 page being pre-rendered" icon="circle-xmark">
    **Cause:** Your app checks authentication or data, then redirects to a 404 or login page before content loads.

    **Fix:**

    * Ensure public pages don't require authentication
    * Don't redirect before content is fetched
    * Return proper 404 status codes (not redirects to a 404 page)

    ```jsx theme={null}
    // Bad - redirects before checking if page exists
    if (!user) {
      navigate('/login');
      return null;
    }

    // Good - show content for public pages regardless of auth
    if (isPublicPage) {
      return <PageContent />;
    }
    ```
  </Accordion>

  <Accordion title="JavaScript chunk loading failures" icon="triangle-exclamation">
    **Cause:** Code-split bundles fail to load during pre-rendering. This typically happens when a new deployment changes chunk hashes but old URLs are still being rendered.

    Common errors:

    * `ChunkLoadError`
    * `Loading chunk [name] failed`
    * `Failed to fetch dynamically imported module`

    **Fix:**

    * Ensure all JS chunks are available at the URLs your app references
    * If using hashed filenames, make sure old chunks remain accessible during deployment transitions
    * Add error boundaries to gracefully handle chunk failures
  </Accordion>

  <Accordion title="Page content below minimum thresholds" icon="text-slash">
    **Cause:** Your page has fewer than 100 characters of visible text or fewer than 20 DOM elements. The renderer keeps waiting for more content until it times out.

    This commonly happens with:

    * Pages that are mostly images or video with minimal text
    * Single-purpose pages (e.g., a login form with just a few fields)
    * Pages where content is rendered inside `<canvas>` or `<iframe>`

    **Fix:** Add visible text content that's relevant for SEO — a heading, description, or alt text that brings the page above the 100-character threshold.
  </Accordion>
</AccordionGroup>

## After Fixing

Once you've fixed the issue in your code:

1. Go to your [Hado SEO Dashboard](https://hadoseo.com/dashboard)
2. Navigate to the affected domain
3. Click **Re-render** on the affected page (or use **Batch Recrawl** for multiple pages)
4. Check the **Render** column to confirm the status changes to OK
5. Verify with [SEO Bot Crawler Test](https://hadoseo.com/free-seo-bot-crawler-test)

<Tip>
  Use the **Render Status** filter on the Pages table to quickly find all pages with rendering issues. See [Understanding Render Status](/guides/troubleshooting/render-status) for a full breakdown of status codes and resolution guidance.
</Tip>
