Skip to content

reduce the time of prerender screen for better seo and crawbility#42

Merged
hrx01-dev merged 5 commits into
hrx01-dev:mainfrom
krishnnag998-del:vite-plugin-ssg
Jun 20, 2026
Merged

reduce the time of prerender screen for better seo and crawbility#42
hrx01-dev merged 5 commits into
hrx01-dev:mainfrom
krishnnag998-del:vite-plugin-ssg

Conversation

@krishnnag998-del

@krishnnag998-del krishnnag998-del commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

DESCRIPTION

close #21
This issue is about SEO and initial page loading, not about a bug in your React code.
BENIFITS
The benefits of implementing are:

-- Better SEO – Search engines can read your page content, improving search rankings.
-- Faster Initial Load – Users see page content sooner because HTML is already generated.
-- Better Social Media Previews – Links shared on WhatsApp, LinkedIn, X, Facebook, etc., show proper titles, descriptions, and images.
-- Improved User Experience – Reduces the blank screen users see while JavaScript loads.
-- Minimal Code Changes – Only a few configuration files need updates; existing React components usually stay unchanged.
-- No Change in Functionality – The application's features and behavior remain the same.
-- Production Ready – Makes the application more optimized for deployment and easier for search engine crawlers to index.
((Puppeteer is a headless browser that opens your website, waits for React to load, and then saves the fully rendered HTML. This pre-rendered HTML is served to search engines and users, which helps reduce the initial blank screen, improves SEO, and creates better social media previews. It requires only minimal changes to the existing React + Vite application without changing its functionality.))

Summary by CodeRabbit

Release Notes

  • New Features

    • Added build-time static pre-rendering using a headless browser to generate ready-to-serve HTML for the homepage.
  • Chores

    • Updated the build pipeline to run the prerender step after the Vite build.
    • Added the required tooling to support prerendering.
  • Bug Fixes

    • Improved app startup robustness by handling cases where the root mount element isn’t found.
    • Enabled client-side routing by wrapping the app in a browser router.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a2665e32-9886-498d-b717-5b3e38e8e8a6

📥 Commits

Reviewing files that changed from the base of the PR and between 73bd6e7 and 1b1a9eb.

📒 Files selected for processing (1)
  • scripts/prerender.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/prerender.js

📝 Walkthrough

Walkthrough

Adds a Puppeteer-based post-build pre-rendering step to fix the blank-page issue for crawlers. A new scripts/prerender.js serves the Vite dist/ output, navigates configured routes, captures rendered HTML, and writes it back to dist/. The build script is updated to run this script after vite build, puppeteer is added as a devDependency, and src/main.tsx is guarded to mount React only when the #root element is present.

Changes

Static Pre-Rendering Pipeline

Layer / File(s) Summary
Build script wiring, dependency, and React root guard
package.json, src/main.tsx
scripts.build is updated to run node scripts/prerender.js after vite build; puppeteer ^25.1.0 is added to devDependencies. src/main.tsx imports BrowserRouter, stores the #root element in a variable, and conditionally mounts the React tree only when the element exists, wrapping <App /> inside <BrowserRouter>, replacing the prior non-null assertion.
Prerender script: config constants and static file server
scripts/prerender.js
Defines the dist path, server port, and target routes as configuration constants. Implements startServer(port) — an HTTP server that maps / and /404.html to their HTML files, resolves other paths under dist/, sets Content-Type by extension, and resolves with the listening server instance.
Prerender orchestration: Puppeteer navigation, HTML capture, and cleanup
scripts/prerender.js
prerender() validates dist/ exists, starts the static server, launches Puppeteer, and for each configured route navigates to the local URL with networkidle2, captures page.content(), ensures the output directory exists, and writes the HTML to dist/. Per-route errors are caught and logged. A finally block closes the browser and server; fatal errors call process.exit(1).

Sequence Diagram(s)

sequenceDiagram
  actor BuildPipeline as npm run build
  participant ViteBuild as vite build
  participant PrerenderScript as scripts/prerender.js
  participant HTTPServer as Static HTTP Server
  participant PuppeteerBrowser as Puppeteer
  participant DistFS as dist/ filesystem

  BuildPipeline->>ViteBuild: vite build
  ViteBuild->>DistFS: write bundled assets
  ViteBuild-->>BuildPipeline: exit 0
  BuildPipeline->>PrerenderScript: node scripts/prerender.js
  PrerenderScript->>DistFS: validate dist/ exists
  PrerenderScript->>HTTPServer: startServer(port)
  HTTPServer-->>PrerenderScript: listening server
  PrerenderScript->>PuppeteerBrowser: launch()
  loop each configured route
    PrerenderScript->>PuppeteerBrowser: page.goto(localhost/route, networkidle2)
    PuppeteerBrowser-->>PrerenderScript: page.content() HTML
    PrerenderScript->>DistFS: write HTML to dist/<route>/index.html
  end
  PrerenderScript->>PuppeteerBrowser: browser.close()
  PrerenderScript->>HTTPServer: server.close()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hrx01-dev/Servio#41: Both PRs modify src/main.tsx to wrap the <App /> render in BrowserRouter, updating the React entrypoint to integrate client-side routing.

Poem

🐇 Hop hop, the crawler came to see,
A blank white page — oh dear, oh me!
Now Puppeteer sneaks through each route,
Captures the HTML, writes it out.
Static HTML, fresh from the warren,
No more blank pages — content is flowin'! 🌿

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title refers to reducing prerender time for SEO, which is only a secondary optimization goal; the core change is implementing prerendering itself to solve the client-only rendering problem. Consider a title more directly reflecting the primary change, such as 'Add pre-rendering for SEO and crawlability' or 'Implement Puppeteer-based pre-rendering for static HTML generation.'
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR successfully implements pre-rendering using Puppeteer via a custom script and satisfies both acceptance criteria: generates static HTML with rendered content and is curl-verifiable.
Out of Scope Changes check ✅ Passed All changes—adding puppeteer dependency, creating prerender script, modifying build process, and wrapping App with BrowserRouter—are directly related to enabling pre-rendering and SEO improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
scripts/prerender.js (1)

18-18: ⚡ Quick win

Use an ephemeral port and explicit listen error handling.

Line 18 hardcodes 3000; if it’s occupied, this step is flaky. Also, startServer should reject on server listen errors instead of relying on implicit process-level behavior. Prefer 0 (OS-assigned port) and build URLs from the actual bound port.

🔧 Suggested refactor
-const PORT = 3000;
+const PORT = Number(process.env.PRERENDER_PORT ?? 0);

 function startServer(port) {
-  return new Promise((resolve) => {
+  return new Promise((resolve, reject) => {
@@
-    server.listen(port, () => {
-      console.log(`[Prerender] Server started on http://localhost:${port}`);
-      resolve(server);
+    server.on('error', reject);
+    server.listen(port, '127.0.0.1', () => {
+      const address = server.address();
+      const actualPort =
+        typeof address === 'object' && address ? address.port : port;
+      console.log(`[Prerender] Server started on http://127.0.0.1:${actualPort}`);
+      resolve({ server, port: actualPort });
     });
   });
 }
@@
-  let server;
+  let server;
+  let serverPort;
@@
-    server = await startServer(PORT);
+    ({ server, port: serverPort } = await startServer(PORT));
@@
-      const url = `http://localhost:${PORT}${route}`;
+      const url = `http://127.0.0.1:${serverPort}${route}`;

Also applies to: 23-23, 56-59, 76-76, 90-90

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/prerender.js` at line 18, Replace the hardcoded PORT constant value
of 3000 with 0 to use an OS-assigned ephemeral port, preventing flakiness when
the port is already occupied. Update the startServer function to explicitly
handle and reject on server listen errors instead of relying on implicit
process-level behavior. Finally, modify all references to PORT (on lines 23,
56-59, 76, and 90) to dynamically use the actual bound port by calling
server.address().port after the server successfully listens, ensuring URLs are
constructed with the correct port regardless of which ephemeral port was
assigned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/prerender.js`:
- Around line 24-29: The http.createServer callback constructs filePath directly
from req.url without validation, creating a directory traversal vulnerability.
Normalize the URL pathname using path.normalize() or similar, then resolve the
complete file path and verify that it remains within DIST_DIR (e.g., by checking
that path.resolve(filePath).startsWith(path.resolve(DIST_DIR))). Only proceed
with file operations like fs.readFile if the resolved path passes this
validation check, preventing requests with traversal segments like /../../../
from accessing files outside the dist directory.
- Around line 117-123: In the prerender.js script, the route pre-rendering loop
catches and logs errors for failed routes but then unconditionally reports
success and exits with code 0. To fix this, create an array to track failed
routes and push route names to it whenever an error is caught in the catch
block. After the try-catch loop completes, check if the failed routes array has
any entries, and if it does, throw an error with details about which routes
failed instead of logging the success message. This ensures the process will
exit with a non-zero code when any route pre-rendering fails, preventing broken
deployments from appearing healthy.

---

Nitpick comments:
In `@scripts/prerender.js`:
- Line 18: Replace the hardcoded PORT constant value of 3000 with 0 to use an
OS-assigned ephemeral port, preventing flakiness when the port is already
occupied. Update the startServer function to explicitly handle and reject on
server listen errors instead of relying on implicit process-level behavior.
Finally, modify all references to PORT (on lines 23, 56-59, 76, and 90) to
dynamically use the actual bound port by calling server.address().port after the
server successfully listens, ensuring URLs are constructed with the correct port
regardless of which ephemeral port was assigned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e6a5eb42-e641-4ed9-b0c4-1f9ab0a8ba93

📥 Commits

Reviewing files that changed from the base of the PR and between 8ed0118 and cd717ad.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • package.json
  • scripts/prerender.js
  • src/main.tsx

Comment thread scripts/prerender.js
Comment thread scripts/prerender.js
@hrx01-dev

Copy link
Copy Markdown
Owner

@krishnnag998-del THIS BRANCH HAS MERGE CONFLICTS , RESOLVE IT FOR THIS PR TO BE MERGED

Bot added 2 commits June 21, 2026 01:49
# Conflicts:
#	package-lock.json
#	package.json
#	src/main.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 88: In scripts/prerender.js, locate the puppeteer launch configuration
that uses the deprecated headless API option (the object with `headless:
'new'`). Replace `headless: 'new'` with `headless: true` to use the modern
headless mode that is compatible with Puppeteer v25.1.0, since Puppeteer v22+ no
longer supports the 'new' string value for the headless option.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ba80e27-8992-4b2d-94a8-70163c9f7a2d

📥 Commits

Reviewing files that changed from the base of the PR and between 1d460fd and 73bd6e7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json
  • src/main.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/main.tsx

Comment thread package.json

@hrx01-dev hrx01-dev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work

@hrx01-dev hrx01-dev merged commit 8ff1694 into hrx01-dev:main Jun 20, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Site is client-rendered only — crawlers & social scrapers get a blank page

2 participants