added dynamic sitemap.xml and robots.txt SEO routes in seo.routes.js - #201
added dynamic sitemap.xml and robots.txt SEO routes in seo.routes.js#201PrinceK-Git wants to merge 6 commits into
Conversation
|
Few critical issues found in this pr: 1. Critical: URL Encoding for Course Codes with SpacesFile: <loc>${BASE_URL}/browse/${course.code}</loc>Some courses in CourseHub have spaces in their codes (e.g.,
Fix: <loc>${BASE_URL}/browse/${encodeURIComponent(course.code.replace(/\s+/g, ""))}</loc>2. Express Middleware Order in
|
a2fdf4b to
0139384
Compare
DreamBot706
left a comment
There was a problem hiding this comment.
1. Remove docs/nginx.conf from this PR
Action: Delete docs/nginx.conf from this branch.
Why:
The docs/nginx.conf you guys added had a bunch of issues and was not consistent with the correct one you are using and dont keep the nginx.conf in the repo.
Don't worry: I've already updated the Nginx files corresponding to your pr.
2. JavaScript .replace() Dollar Sign ($) Bug
File: server/middleware/seo.js (lines 91–99)
let modified = html.replace(
/<title>CourseHub<\/title>/i,
`<title>${title}</title>`,
);
modified = modified.replace(
/<meta\s+name="description"[^>]*\/?>/i,
injectedBlock,
);Why this breaks:
In JavaScript, String.prototype.replace(regex, string) treats the $ character in the replacement string as a special token ($1 = regex capture group, $& = match, etc.). If any course name or JSON-LD field contains a dollar sign or math symbol ($), JavaScript will treat it as a token and silently delete or corrupt all characters following it.
Fix: Pass a replacer function instead of a raw string:
let modified = html.replace(
/<title[^>]*>[\s\S]*?<\/title>/i,
() => `<title>${title}</title>`,
);
modified = modified.replace(
/<meta\s+name="description"[^>]*\/?>/i,
() => injectedBlock,
);3. Browser Tab Titles for Humans (CourseHub & CS101 | CourseHub)
Remember the separation of concerns:
- Crawlers/Bots need the long, keyword-stuffed title for Google indexing (
CS101 - Introduction to Computing Study Materials | CourseHub IIT Guwahati). Express middleware handles this. - Human Students need clean, short, readable tab titles in their browser.
A. Landing Page (client/src/screens/landing/index.jsx):
The title currently set ("CourseHub | IIT Guwahati Study Materials, Notes & PYQs") is way too long for a browser tab and gets cut off. For humans, keep it clean and simple as just CourseHub. Also, please fix the indentation in that useEffect block.
In client/src/screens/landing/index.jsx:
useEffect(() => {
document.title = "CourseHub";
}, []);B. Course Browse Page (client/src/screens/browse/index.jsx):
Right now, browse/index.jsx doesn't update document.title at all — when a student browses a course, the tab just stays generic. When viewing a course, the tab should update to ${courseCode} | CourseHub (e.g. CS101 | CourseHub).
In client/src/screens/browse/index.jsx, add:
useEffect(() => {
if (code) {
document.title = `${code.toUpperCase()} | CourseHub`;
} else {
document.title = "CourseHub";
}
return () => {
document.title = "CourseHub";
};
}, [code]);4. Google Search Console URL Inspection Tool is Bypassing the Middleware
File: server/middleware/seo.js (lines 26–27)
BOT_UA_REGEX currently only looks for googlebot. However, when you go into Google Search Console and click "Test Live URL" or "Inspect URL", Google uses:
Mozilla/5.0 ... (compatible; Google-InspectionTool/1.0;)
googlebot does not match Google-InspectionTool. GSC will be treated as a human, bypass your middleware, and report that the page has no metadata!
Fix: Update BOT_UA_REGEX to include GSC's tool, Lighthouse, and missing crawlers:
const BOT_UA_REGEX =
/googlebot|google-inspectiontool|chrome-lighthouse|storebot-google|bingbot|slurp|duckduckbot|baiduspider|yandexbot|facebot|facebookexternalhit|twitterbot|discordbot|whatsapp|telegrambot|linkedinbot|slackbot|applebot|ia_archiver|msnbot|ahrefsbot|semrushbot|dotbot|rogerbot|360spider|sogou|bytespider/i;5. In-Memory Query Caching on SEO Middleware (Performance & DB Protection)
File: server/middleware/seo.js (lines 116–127)
Right now, every bot request to /browse/<code> fires an unindexed $regex query in MongoDB (flexPattern). Course codes change at most once per semester. If an aggressive crawler or scraper hits hundreds of course links, these regex queries will create unnecessary database load.
Fix: Add a simple in-memory cache (a Map with a timestamp TTL or simple object cache) for course code lookups:
const courseCache = new Map(); // key: normalizedCode -> { course, expiresAt }
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hourCheck the cache before querying MongoDB, and store the result.
6. Missing og-image.png Asset
File: server/middleware/seo.js (line 60)
const ogImage = `${BASE_URL}/og-image.png`;og-image.png doesn't actually exist in the repo. When links are shared on WhatsApp or Discord, the crawler asks for https://coursehub.codingclub.in/og-image.png and gets a 404, so link preview thumbnails fail.
Fix: Create/export a 1200×630 banner and place it at client/public/og-image.png so Vite copies it to the web root during build.
7. Quick Polish & Defensive Coding
-
Add Canonical Link: In
server/middleware/seo.jsinsideinjectedBlock, add:<link rel="canonical" href="${courseUrl}" />
This prevents Google from penalizing duplicate URLs (
/browse/cs101vs/browse/CS101). -
Vary Header on 404: In
server/middleware/seo.js(line 130), before returningres.status(404).send(...), add:res.set("Vary", "User-Agent");
Otherwise, upstream caches like Cloudflare might cache the 404 HTML and serve it to humans.
-
Cache-Control for
robots.txt: Inserver/modules/seo/seo.routes.js(line 66), add:res.set("Cache-Control", "public, max-age=86400, s-maxage=86400");
Matches the 24-hour cache header already on
sitemap.xmlso crawlers don't repeatedly hit Express. -
Consolidate
BASE_URL: Inserver/middleware/seo.jsandserver/modules/seo/seo.routes.js,BASE_URLis hardcoded. Useconfig.clientURLfromserver/config/default.js(orprocess.env.CLIENT_URL || "https://coursehub.codingclub.in"). -
Defensive Check in Sitemap: In
server/modules/seo/seo.routes.js(line 26), add:for (const course of courses) { if (!course?.code) continue; ...
If any legacy course record in MongoDB has a null/empty code,
course.code.replace()will throw a TypeError and crash the entire/sitemap.xmlrequest. -
cachedIndexHtmlRefresh: Inserver/middleware/seo.js,cachedIndexHtmlis cached indefinitely in memory. If a frontend-only deploy runs without a backend PM2 reload, Express will serve stale asset references to bots. Give it a simple TTL (e.g. re-read every 10 minutes) or check filemtime.
Added two new Express routes for SEO:
/sitemap.xml— Dynamically queries MongoDB and generates an XML sitemaplisting all course pages. Auto-updates when new courses are added.
/robots.txt— Tells search engine bots which pages to crawl (course pages)and which to skip (dashboard, profile, API, admin).
Files changed:
Part of Issue #190 (SEO Implementation)