[Next.js] Was the cause of OGP not appearing a 'loading screen'? Implementation Mistakes that Kill SEO and Their Solutions

[Next.js] Was the cause of OGP not appearing a 'loading screen'? Implementation Mistakes that Kill SEO and Their Solutions のビジュアル

Have you encountered problems on a Next.js-built site where 'OGP doesn't appear even though it's visible in the browser' or 'Google doesn't index it'?
The actual cause might be in the 'implementation method of the loading screen'.
This article explains a failed case where an 'empty HTML' was returned to crawlers during SSR (Server-Side Rendering), the investigation method using curl commands, and the correct implementation pattern with code examples.

  • 見えざるSEO事故ブラウザで正常でも、SSRの記述ミスによりクローラーにコンテンツが渡っていないケースがある
  • 調査の鍵はcurlブラウザの検証ツールではなく、curlコマンドでボット視点の「生のHTML」を確認することが重要
  • 条件付きレンダリングの罠認証チェックなどで安易に return を分けると、サーバー側でコンテンツ描画がスキップされる
  • 解決策はオーバーレイコンテンツを常にレンダリングしつつ、ローディング画面をCSSで前面に「重ねる」実装が正解

Introduction: A site that works perfectly, but no OGP?

One of the most terrifying bugs in web development is a bug where "the site works perfectly in the browser, but there's a fatal problem occurring behind the scenes."

This time, I'll share a story about a "mistake in implementing a loading screen that nearly wiped out SEO" which I actually encountered in a Next.js project.

If you're struggling with symptoms like "OGP not being reflected after building a site with Next.js" or "favicons not appearing in Google search results," this article should provide a clue to the solution.

The Incident: "Not Recognized" Even with Status Code 200

The site construction was completed, and it was deployed to the production environment. I checked its operation in the browser. The animations were smooth, page transitions were lightning fast. I thought it was "perfect."

However, the moment I tried to share it on social media, I noticed something unusual.

  • OGP images were not displayed even when posting URLs on Twitter (X) or Facebook.
  • Favicons and descriptions were not reflected in Google search results, no matter how long I waited.

When I hit the URL with Facebook's debugger tool, the HTTP status code returned 200 OK. The server was alive. But the title and image information showed "could not be retrieved."

Debugging Gone Astray: Days of Suspecting Authentication and Caching

Initially, I suspected the Next.js and Vercel settings.

  • "Is Vercel's Authentication (e.g., Basic Auth) accidentally ON?" → Checked, it was OFF.
  • "Is the CSP (Content Security Policy) header too strict and blocking crawlers?" → Tried disabling it, but no change.
  • "Is it just bot cache remaining?" → Accessed with different URL parameters, but the situation didn't change.

Even after reviewing the configuration files, I couldn't find any suspicious areas. I was completely lost in a maze.

Decisive Evidence: The "Void" Seen by the curl Command

"Why can't crawlers see what's visible in the browser?"

Returning to basics, I decided to look at the site from a "bot's perspective." What helped here was not the browser's inspection tool, but the curl command executed in the terminal.

I tried accessing it using the following command, impersonating Facebook's crawler (facebookexternalhit).

I was astonished when I saw the returned HTML.

The content was completely empty. There was no title or OGP tag within the <head>, and only the text "Loading..." in the body. With this, there was no way a crawler could understand the site's content.

A Trap Not Noticed with Browser Developer Tools Alone

Normally, we tend to check HTML using "Developer Tools" (DevTools) in browsers like Chrome. However, modern browsers are smart and display the DOM "after" JavaScript has been executed.

In contrast, many crawlers (especially OGP fetching bots) make judgments based on the initial HTML returned from the server (initial response). There was a critical discrepancy here.

The Real Culprit: Conditional Rendering during SSR

The cause was in how conditional rendering was written in _app.tsx, which is the root of the application.

When checking authentication status, I had implemented it to "only show a loading screen during the check" in an attempt to improve the user experience.

❌ The Culprit Code

Why is this bad?

  1. Hooks such as useAuth and useEffect are executed only on the client-side (browser).
  2. At the time of Next.js's SSR (Server-Side Rendering), isChecking proceeds with its initial value (e.g., true).
  3. The server follows if (true) and generates an HTML response containing only <Preloader />.
  4. The crawler receives this "Preloader-only HTML" and returns, judging it to be an "empty page."

In a browser, which humans see, the content switches after JavaScript runs, so we don't notice, but for bots, it was a "permanently loading page."

Solution: "Overlay" Instead of "Switching"

The solution was simple. It was to "always render the content (Component) regardless of whether it's loading." After that, I changed the implementation to overlay the loading screen using CSS.

⭕️ Correct Code

CSS Control (Preloader)

The Preloader component is styled to cover the entire screen with properties like position: fixed; z-index: 9999;. This maintains the visual behavior (content is not visible during loading) while creating a state where the content exists in the HTML structure.

Immediately after deploying this fix, checking with the curl command confirmed that <title> and <meta property="og:image"> were properly output, and social media display was successfully restored.

Summary: Treating SSR with a React Mindset Can Kill Your SEO

When accustomed to developing only SPAs (Single Page Applications) or with just React, it's common to "separate returns based on conditions." However, with frameworks that involve SSR like Next.js, you must always be conscious of "what is included in the initial HTML returned by the server."

Lessons learned this time:

  1. Be wary of conditional rendering: Check that content is not excluded during SSR.
  2. The curl command is your friend: For SEO-related troubleshooting, get in the habit of checking the raw HTML with curl instead of the browser.

If you're wondering "why isn't OGP appearing?", try accessing your site from the terminal once. You might discover an unexpected "void."

TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発 [ 手島 拓也 ]

TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発 [ 手島 拓也 ]

参考リンク

Was this article helpful?