Next.js and React are fast, elegant, and powerful. They are also dangerously easy to misconfigure.After reviewing dozens of production React and Next.js applications, I've noticed a pattern:
Most security issues are not caused by hackers. They are caused by developers trusting the framework too much.
This article walks through the most common real-world security vulnerabilities in React and Next.js apps, why they happen, and how to fix them properly — not just silence the warning.
1️⃣ XSS (Cross-Site Scripting): "React Protects Me" Is a Half-Truth
XSS is an attack where malicious JavaScript is injected into your page and runs in your users' browsers.
Yes, React escapes JSX by default.
No, your app is not automatically safe.
The dangerous pattern
<div dangerouslySetInnerHTML={{ __html: content }} />This single line has caused countless real-world breaches.
The correct fix
If you must render HTML, sanitize it:
import DOMPurify from "dompurify";
const safeHTML = DOMPurify.sanitize(content);Rule: If user input touches innerHTML, sanitize it — or assume compromise.
6,500+ Tech Professional Courses. Upgrade your skills before opportunities slip away. Free 10-day access.
2️⃣ Unprotected API Routes: The Silent Data Leak
In Next.js, API routes feel "hidden".
They are not.
Attackers don't browse your UI. They enumerate endpoints.
export default function handler(req, res) {
res.json({ adminData: true });
}Proper fix: Authentication + Authorization
if (!user || user.role !== "admin") {
return res.status(403).end();
}Client-side checks are UX. Server-side checks are security.
3️⃣ Environment Variables: How Secrets Go Public
This mistake happens in real production apps:
NEXT_PUBLIC_API_SECRET=super-secret
NEXT_PUBLIC_DB_PASSWORD=password123Anything prefixed with NEXT_PUBLIC_ is sent to the browser.
Anyone can read it. Forever.
✅ Proper fix
API_SECRET=super-secret
DB_PASSWORD=password123
NEXT_PUBLIC_API_BASE_URL=https://api.example.comRule of thumb
If the browser does not absolutely need it, do not prefix it with
NEXT_PUBLIC_.
Best practice
- Public: feature flags, public URLs
- Private: tokens, credentials, secrets
If the browser doesn't need it, don't expose it.
4️⃣ CSRF (Cross-Site Request Forgery): Still Dangerous
CSRF lets attackers perform actions on behalf of logged-in users without stealing credentials.
If you use cookie-based authentication, you are exposed.
Fix
- CSRF tokens
- SameSite cookies
- Libraries like NextAuth (which handle this correctly)
If auth uses cookies, CSRF protection is mandatory.
5️⃣ SSR Data Leaks: getServerSideProps Is Not Private
Developers assume server-side rendering (SSR) means secrecy.
It doesn't.
Anything returned here:
return {
props: {
user: fullUserObject,
},
};Ends up in:
- HTML
- Page source
- The browser
Correct approach
return {
props: {
user: {
id: user.id,
name: user.name,
},
},
};Return only what the page needs.
SSR does not mean secret.
6️⃣ No Rate Limiting = Free Brute Force
Login, reset password, verification endpoints…
Without rate limiting, bots can try thousands of attempts.
Minimum defense
- IP limits
- User limits
- Temporary bans
- Basic throttling
This alone stops most automated attacks.
7️⃣ Open Redirects: A Phishing Dream
router.push(req.query.redirect);Attackers abuse this to redirect users from your trusted domain to malicious sites.
Fix
Use allowlists:
const allowedRoutes = ["/dashboard", "/profile"];Never trust redirect URLs from users.
8️⃣ Dependencies: Your Weakest Link
You didn't write all your code.
Attackers know this.
What you must do
npm audit- Dependabot / Snyk
- Aggressive updates
Most real-world breaches come from known, unpatched vulnerabilities.
9️⃣ Missing Security Headers (They Matter More Than You Think)
At minimum:
- Content Security Policy (CSP)
- X-Frame-Options
- Referrer-Policy
These prevent entire classes of attacks before they start.
🔟 The Biggest Mistake: Trusting the Frontend
if (user.isAdmin) {
showAdminPanel();
}Good UX.
Zero security.
Authorization must always live on the server.
✅ Correct approach: Authorization must live on the server
The frontend is only a visual layer. Real security happens in your API.
Next.js API Route — where the real check happens
export default async function handler(req, res) {
const user = await getUserFromSession(req);
if (!user || user.role !== "admin") {
return res.status(403).json({ message: "Forbidden" });
} // Admin action happens here
return res.status(200).json({ secretAdminData: true });
}Final Thoughts
Frameworks don't make apps secure.
Developers do.
Next.js and React give you powerful tools — and enough freedom to break your security model if you're careless.
Assume every client is hostile. Validate, sanitize, and authorize everything.
Call to Action
If this article changed how you think about React security:
- 👏 Clap to support security education
- 🔁 Share it with your team
- 🧠 Follow for more deep-dive engineering content
Secure code is not paranoia. It's professionalism.
References & Further Reading
- OWASP Top 10 Web Application Security Risks
- React Official Documentation — Security
- Next.js Documentation — Security Best Practices
- Angular Security Guide
- MDN Web Docs — Content Security Policy (CSP)
- Node.js Security Best Practices
- DOMPurify Documentation
- NextAuth Security Model
Thank you for being a part of the community
Before you go:

👉 Be sure to clap and follow the writer ️👏️️
👉 CodeToDeploy Tech Community is live on Discord — Join now!
Note: This Post may contain affiliate links.