Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions client/src/screens/error/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import React from "react";
import React, { useEffect } from "react";

const ErrorScreen = () => {
useEffect(() => {
document.title = "Page Not Found | CourseHub";
return () => {
document.title = "CourseHub";
};
}, []);

return (
<div
style={{
Expand All @@ -14,4 +21,4 @@ const ErrorScreen = () => {
);
};

export default ErrorScreen;
export default ErrorScreen;
7 changes: 6 additions & 1 deletion client/src/screens/landing/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ const LandingPage = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);

useEffect(() => {
document.title = "CourseHub | IIT Guwahati Study Materials, Notes & PYQs";
return () => {
document.title = "CourseHub";
};
}, []);
useEffect(() => {
clearLegacySessionLocalCoursesCache();
}, []);
Expand Down
160 changes: 160 additions & 0 deletions docs/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# =============================================================================
# CourseHub — Nginx Server Block (SEO-Aware Configuration)
# =============================================================================
#
# WHAT CHANGED (vs the old config):
# NEW location /browse/ → proxied to Express (SEO bot-detection middleware)
# NEW location /sitemap.xml → proxied to Express (dynamic sitemap — Prince)
# NEW location /robots.txt → proxied to Express (crawler directives — Prince)
#
# HOW TO APPLY:
# 1. Copy this file to /etc/nginx/sites-available/coursehub
# 2. sudo ln -s /etc/nginx/sites-available/coursehub /etc/nginx/sites-enabled/
# 3. sudo nginx -t <- always test before reloading
# 4. sudo systemctl reload nginx
#
# PREREQUISITES:
# - Express server running on 127.0.0.1:8080
# - React build output copied to /var/www/coursehub/ (index.html + assets/)
# - SSL certificate at the paths below (Let's Encrypt / Certbot recommended)
# =============================================================================

upstream coursehub_express {
server 127.0.0.1:8080;
keepalive 64;
}

# -- HTTP -> HTTPS redirect ---------------------------------------------------
server {
listen 80;
listen [::]:80;
server_name coursehub.codingclub.in;

# Allow Let's Encrypt ACME challenge through
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}

# -- Main HTTPS server block --------------------------------------------------
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name coursehub.codingclub.in;

# -- SSL ------------------------------------------------------------------
ssl_certificate /etc/letsencrypt/live/coursehub.codingclub.in/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/coursehub.codingclub.in/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

# -- Root: React static build ---------------------------------------------
root /var/www/coursehub;
index index.html;

# -- Gzip compression -----------------------------------------------------
gzip on;
gzip_types text/plain text/css application/javascript application/json
application/xml image/svg+xml;
gzip_min_length 1024;

# -- Security headers -----------------------------------------------------
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# =========================================================================
# SEO ROUTES — proxied to Express
# =========================================================================

# [NEW] /browse/* — Express runs the bot-detection SEO middleware here.
# Bots -> receive modified HTML with course-specific metadata.
# Humans -> Express calls next(), returns the normal React index.html.
location /browse/ {
proxy_pass http://coursehub_express;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Pass the original User-Agent so the middleware can detect bots
proxy_set_header User-Agent $http_user_agent;

# Timeout safety — MongoDB query should be fast
proxy_read_timeout 10s;
proxy_connect_timeout 5s;
}

# [NEW] /sitemap.xml — dynamic XML generated from MongoDB (Prince's route)
location = /sitemap.xml {
proxy_pass http://coursehub_express;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Cache sitemap at Nginx layer for 24 h (Express also sets Cache-Control)
proxy_cache_valid 200 24h;
}

# [NEW] /robots.txt — served by Express (Prince's route)
location = /robots.txt {
proxy_pass http://coursehub_express;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# =========================================================================
# API ROUTES — proxied to Express (unchanged from before)
# =========================================================================
location /api/ {
proxy_pass http://coursehub_express;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;

# Needed for file upload endpoints
client_max_body_size 50M;
}

# =========================================================================
# STATIC ASSETS — served directly by Nginx (fast path, no Express involved)
# =========================================================================

# Vite build assets: JS/CSS bundles — long-lived cache (content-hashed filenames)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}

# Favicon and other root-level static files
location ~* \.(ico|png|svg|webp|woff2|woff|ttf)$ {
expires 30d;
add_header Cache-Control "public";
try_files $uri =404;
}

# =========================================================================
# SPA FALLBACK — everything else serves index.html (React Router handles it)
# =========================================================================
location / {
try_files $uri $uri/ /index.html;
}
}
11 changes: 10 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import fileRoutes from "./modules/file/file.routes.js";
import folderRoutes from "./modules/folder/folder.routes.js";
import yearRoutes from "./modules/year/year.routes.js";
import studentRoutes from "./modules/student/student.routes.js";
import seoRoutes from "./modules/seo/seo.routes.js";
import seoMiddleware from "./middleware/seo.js";

const app = express();
const server = http.createServer(app);
Expand Down Expand Up @@ -61,6 +63,8 @@ app.use("/api/files", fileRoutes);
app.use("/api/folder", folderRoutes);
app.use("/api/year", yearRoutes);
app.use("/api/student", studentRoutes);
app.use(seoRoutes);

app.use(
"/homepage",
catchAsync(async (req, res) => {
Expand All @@ -70,6 +74,10 @@ app.use(
}),
);

app.use("/browse", seoMiddleware);

app.get("*", (req, res) => res.sendFile(path.resolve(__dirname, "static", "index.html")));

app.use((error, req, res, next) => {
logger.error("Unhandled request error", {
error,
Expand All @@ -83,7 +91,6 @@ app.use((error, req, res, next) => {
const { status = 500, message = "Something went wrong!" } = error;
return res.status(status).json({ error: true, message });
});
app.get("*", (req, res) => res.sendFile(path.resolve(__dirname, "static", "index.html")));

async function closeServer() {
if (!server.listening) return;
Expand Down Expand Up @@ -129,6 +136,8 @@ process.once("SIGTERM", () => void terminate({ signal: "SIGTERM", exitCode: 0 })
process.once("uncaughtException", (error) => void terminate({ error, exitCode: 1 }));
process.once("unhandledRejection", (error) => void terminate({ error, exitCode: 1 }));



export async function start() {
await connectDatabase();
scheduler = initScheduler();
Expand Down
Loading
Loading