Top 50 React JS Interview Questions
The most important React interview questions for 2026 — covering hooks, state management, component lifecycle, performance, and React Router. Asked at startups, product companies, and service companies hiring React developers.
React Basics
Q1–Q15Q1What is React? Why is it popular?Easy▼
React is a JavaScript library (not a full framework) built by Meta for building user interfaces. It renders UI as a tree of components.
Why popular:
- Component-based — reusable UI building blocks
- Virtual DOM — efficient updates (only changes what's needed)
- One-way data flow — predictable, easier to debug
- Huge ecosystem — React Router, Redux, Next.js, React Native
- Job market — most in-demand frontend framework in India
Q2What is JSX?Easy▼
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like code inside JavaScript. It gets compiled to React.createElement() calls by Babel.
// JSX
const element = <h1 className="title">Hello, {name}!</h1>;
// What Babel compiles it to:
const element = React.createElement("h1", { className: "title" }, "Hello, " + name + "!");
JSX rules: Use className not class. Self-close empty tags (<img />). Return one root element (or use a Fragment <>...</>).
Q3What is the Virtual DOM and how does it work?Medium▼
The Virtual DOM is a lightweight in-memory copy of the real DOM. When state changes:
- React creates a new Virtual DOM tree
- Compares it with the previous Virtual DOM (diffing)
- Calculates the minimum set of changes needed (reconciliation)
- Updates only those specific parts of the real DOM
This is faster than directly manipulating the real DOM for every change because DOM operations are expensive.
Q4What is the difference between a functional and class component?Easy▼
// Functional component (modern — use this)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Class component (legacy)
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
Functional components are the modern standard. They support hooks, are simpler, and easier to test. Class components are only seen in older codebases. Interviewers expect you to know both but write functional.
Q5What are props in React?Easy▼
Props (properties) are how a parent component passes data to a child component. Props are read-only — a child cannot modify its own props.
// Parent
function App() {
return <Card title="HTML Tutorial" lessons={25} />;
}
// Child
function Card({ title, lessons }) {
return (
<div>
<h2>{title}</h2>
<p>{lessons} lessons</p>
</div>
);
}
Q6What is state in React? How is it different from props?Easy▼
function Counter() {
const [count, setCount] = useState(0); // state
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
- State — internal, mutable, managed by the component itself
- Props — external, immutable, passed from parent
When state changes, the component re-renders. When props change (parent re-renders), child also re-renders.
Q7What is one-way data flow in React?Medium▼
In React, data flows in one direction — from parent to child via props. A child cannot directly modify parent state.
To send data from child to parent, the parent passes a callback function as a prop:
function Parent() {
const [name, setName] = useState("");
return <Child onNameChange={setName} />;
}
function Child({ onNameChange }) {
return (
<input onChange={e => onNameChange(e.target.value)} />
);
}
This makes data flow predictable and bugs easier to trace.
Q8What are React keys? Why are they important?Medium▼
Keys help React identify which items in a list changed, were added, or removed. They must be unique among siblings.
// Wrong — using index as key (bad for reorderable lists)
{items.map((item, index) => <li key={index}>{item}</li>)}
// Correct — use a unique, stable id
{items.map(item => <li key={item.id}>{item.name}</li>)}
Without keys, React may re-render the wrong elements. Using array index as key breaks if the list is reordered or filtered.
Q9What is conditional rendering in React?Easy▼
// if/else (inside function, before return)
if (isLoggedIn) return <Dashboard />;
return <Login />;
// Ternary (inline)
return <div>{isLoggedIn ? <Dashboard /> : <Login />}</div>;
// Short-circuit (render or nothing)
return <div>{error && <ErrorMessage msg={error} />}</div>;
// Nullish — render nothing
return <div>{isLoading ? <Spinner /> : null}</div>;
Q10What is a React Fragment?Easy▼
A Fragment lets you return multiple elements from a component without adding an extra DOM node.
// Without Fragment — adds unnecessary <div>
return (
<div>
<h1>Title</h1>
<p>Paragraph</p>
</div>
);
// With Fragment — no extra DOM element
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
// Or explicit (needed when you need a key prop)
return (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.def}</dd>
</React.Fragment>
);
Q11What is the difference between controlled and uncontrolled components?Medium▼
// Controlled — React controls the value via state
function ControlledInput() {
const [value, setValue] = useState("");
return <input value={value} onChange={e => setValue(e.target.value)} />;
}
// Uncontrolled — DOM controls the value, accessed via ref
function UncontrolledInput() {
const inputRef = useRef();
const handleSubmit = () => console.log(inputRef.current.value);
return <input ref={inputRef} defaultValue="initial" />;
}
Use controlled components for most cases — they give you full control over form data. Uncontrolled is useful for file inputs or when integrating with non-React code.
Q12What is prop drilling? How do you solve it?Medium▼
Prop drilling is passing props through multiple intermediate components that don't need them, just to get data to a deeply nested component.
// Problem: theme passed through every level
<App theme="dark">
<Layout theme="dark">
<Sidebar theme="dark">
<Button theme="dark" /> {/* only Button needs it */}
</Sidebar>
</Layout>
</App>
// Solution 1: React Context
const ThemeContext = createContext("light");
// wrap App with ThemeContext.Provider, use useContext(ThemeContext) in Button
// Solution 2: State management (Redux, Zustand)
Q13What is React Context API?Medium▼
// 1. Create context
const ThemeContext = createContext("light");
// 2. Provide value (wrap your tree)
function App() {
const [theme, setTheme] = useState("dark");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Layout />
</ThemeContext.Provider>
);
}
// 3. Consume anywhere in the tree
function Button() {
const { theme } = useContext(ThemeContext);
return <button className={theme}>Click</button>;
}
Context avoids prop drilling. Don't overuse it — it causes all consumers to re-render on every change.
Q14What are default props and PropTypes?Easy▼
import PropTypes from "prop-types";
function Card({ title, count }) {
return <div>{title}: {count}</div>;
}
// Default props (modern: use default params)
Card.defaultProps = { count: 0 };
// Or: function Card({ title, count = 0 }) { ... }
// PropTypes — runtime type checking
Card.propTypes = {
title: PropTypes.string.isRequired,
count: PropTypes.number,
};
In TypeScript projects, use TypeScript interfaces instead of PropTypes.
Q15What is the React component lifecycle?Medium▼
Three phases:
- Mounting — component is created and inserted into DOM (
constructor→render→componentDidMount) - Updating — state/props change, component re-renders (
render→componentDidUpdate) - Unmounting — component is removed from DOM (
componentWillUnmount)
In functional components, all three are handled with useEffect:
useEffect(() => {
// Mount: run after first render
fetchData();
return () => {
// Unmount: cleanup
cleanup();
};
}, []); // [] = run only once
React Hooks
Q16–Q30Q16What are React hooks? What are the rules of hooks?Easy▼
Hooks are functions that let you use React features (state, lifecycle, context) inside functional components.
Built-in hooks: useState, useEffect, useContext, useRef, useMemo, useCallback, useReducer, useId
Rules of hooks:
- Only call hooks at the top level — not inside loops, conditions, or nested functions
- Only call hooks from React functions (components or custom hooks)
Q17Explain useState in detail.Easy▼
const [state, setState] = useState(initialValue);
// Examples
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);
const [items, setItems] = useState([]);
// Update
setCount(5);
setCount(prev => prev + 1); // functional update (safe with stale closures)
// Object state — must spread existing state
const [form, setForm] = useState({ name: "", email: "" });
setForm(prev => ({ ...prev, name: "Priya" }));
State updates are asynchronous — React batches them for performance. Always use the functional form setState(prev => ...) when new state depends on old state.
Q18Explain useEffect and its dependency array.Medium▼
// Run after every render
useEffect(() => { doSomething(); });
// Run only once (on mount)
useEffect(() => { fetchData(); }, []);
// Run when specific values change
useEffect(() => { fetchUser(userId); }, [userId]);
// Cleanup (runs before next effect + on unmount)
useEffect(() => {
const timer = setInterval(() => tick(), 1000);
return () => clearInterval(timer); // cleanup!
}, []);
Common uses: API calls, subscriptions, DOM manipulation, timers, event listeners.
Q19What is useRef? What are its use cases?Medium▼
// Use case 1: access DOM elements
function TextInput() {
const inputRef = useRef(null);
const focus = () => inputRef.current.focus();
return <><input ref={inputRef} /><button onClick={focus}>Focus</button></>;
}
// Use case 2: store mutable value that doesn't cause re-render
function Timer() {
const countRef = useRef(0);
const tick = () => { countRef.current++; }; // no re-render!
}
// Use case 3: store previous state
const prevCount = useRef(count);
useEffect(() => { prevCount.current = count; }, [count]);
Key: ref.current changes do NOT cause a re-render (unlike state).
Q20What is useMemo? When should you use it?Hard▼
// Without useMemo — recalculates on every render
const sorted = items.sort((a, b) => a - b); // expensive!
// With useMemo — only recalculates when items changes
const sorted = useMemo(() => {
return [...items].sort((a, b) => a - b);
}, [items]);
Use useMemo when:
- A computation is expensive (sorting large arrays, complex calculations)
- A value is used as a dependency in other hooks
- A referentially stable object needs to be passed as a prop
Don't overuse — premature optimization. Profile first.
Q21What is useCallback? How is it different from useMemo?Hard▼
// useCallback — memoizes a FUNCTION
const handleClick = useCallback(() => {
doSomething(id);
}, [id]); // returns same function reference when id doesn't change
// useMemo — memoizes a VALUE
const total = useMemo(() => items.reduce((a, b) => a + b, 0), [items]);
// Memory tip:
// useCallback(fn, deps) === useMemo(() => fn, deps)
Use useCallback when passing callbacks to child components wrapped in React.memo — prevents unnecessary re-renders.
Q22What is useReducer? When do you use it over useState?Hard▼
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case "increment": return { ...state, count: state.count + state.step };
case "reset": return initialState;
case "setStep": return { ...state, step: action.payload };
default: throw new Error("Unknown action");
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
}
Use useReducer when: state has multiple sub-values, next state depends on previous, or complex update logic (like Redux at a small scale).
Q23What are custom hooks?Medium▼
Custom hooks are functions that start with use and contain reusable stateful logic using other hooks.
// Custom hook — reusable fetch logic
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(d => { setData(d); setLoading(false); })
.catch(e => { setError(e); setLoading(false); });
}, [url]);
return { data, loading, error };
}
// Usage in any component
const { data, loading } = useFetch("/api/users");
Q24What is useContext?Medium▼
const UserContext = createContext(null);
// Provider (usually in App.jsx)
function App() {
const [user, setUser] = useState({ name: "Priya" });
return (
<UserContext.Provider value={{ user, setUser }}>
<Router />
</UserContext.Provider>
);
}
// Consumer (any deeply nested component)
function Profile() {
const { user } = useContext(UserContext);
return <h1>{user.name}</h1>;
}
Q25What is React.memo?Hard▼
React.memo is a higher-order component that memoizes a component — it only re-renders if its props change.
// Without memo — re-renders every time parent renders
function ExpensiveList({ items }) {
return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}
// With memo — only re-renders if items prop changes
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
});
Combine with useCallback for function props — otherwise the callback creates a new reference every render, defeating memo.
Q26What is useLayoutEffect? How is it different from useEffect?Hard▼
useEffect: fires asynchronously AFTER the browser paints — safe for data fetching, subscriptionsuseLayoutEffect: fires synchronously AFTER DOM mutations but BEFORE browser paints — use for DOM measurements to prevent visual flicker
// Use useLayoutEffect for DOM measurements
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setHeight(height); // set before paint — no flicker
}, []);
Default to useEffect. Only use useLayoutEffect if you see visual flickering from DOM reads/writes.
Q27What is useId?Medium▼
function FormField({ label }) {
const id = useId(); // stable unique id across server/client
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
// Renders: id="r:1", "r:2", etc. — unique per component instance
Added in React 18. Use for connecting form labels to inputs when you need a stable ID that works with server-side rendering.
Q28What is useImperativeHandle?Hard▼
// Expose specific methods from child to parent
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ""; }
}));
return <input ref={inputRef} />;
});
// Parent
function Form() {
const inputRef = useRef();
return (
<>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);
}
Q29What causes infinite loops in useEffect?Medium▼
// ❌ Infinite loop — no dependency array
useEffect(() => {
setCount(count + 1); // triggers re-render → effect runs again...
});
// ❌ Infinite loop — object/function dependency recreated every render
useEffect(() => {
fetchData(options);
}, [options]); // options = {} recreated on every render
// ✅ Fix — use functional update
useEffect(() => {
setCount(prev => prev + 1);
}, []); // only runs once
// ✅ Fix — memoize object dependency
const options = useMemo(() => ({ page: 1 }), []);
Q30What is useTransition and useDeferredValue? (React 18)Hard▼
// useTransition — mark state update as non-urgent
function Search() {
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setQuery(e.target.value); // urgent (input)
startTransition(() => {
setResults(filter(e.target.value)); // non-urgent (heavy)
});
};
}
// useDeferredValue — defer updating a value
function Results({ query }) {
const deferredQuery = useDeferredValue(query); // lags behind by one render
const filtered = useMemo(() => heavyFilter(deferredQuery), [deferredQuery]);
return <List items={filtered} />;
}
Both are React 18 Concurrent Mode features for keeping the UI responsive during heavy renders.
Advanced React
Q31–Q50Q31What is React Router? How do you set it up?Medium▼
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
Q32What are React Router hooks — useNavigate, useParams, useLocation?Medium▼
import { useNavigate, useParams, useLocation } from "react-router-dom";
function UserProfile() {
const { id } = useParams(); // /user/42 → id = "42"
const navigate = useNavigate(); // programmatic navigation
const location = useLocation(); // current URL info
return (
<>
<p>User ID: {id}</p>
<p>Current path: {location.pathname}</p>
<button onClick={() => navigate("/")}>Go Home</button>
<button onClick={() => navigate(-1)}>Go Back</button>
</>
);
}
Q33What is code splitting and lazy loading in React?Hard▼
import { lazy, Suspense } from "react";
// Lazy load component — only downloads when needed
const Dashboard = lazy(() => import("./Dashboard"));
const Settings = lazy(() => import("./Settings"));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
Code splitting reduces the initial bundle size — users download only the code needed for the current page.
Q34What is an Error Boundary?Hard▼
Error boundaries catch JavaScript errors in child components and show a fallback UI instead of crashing the whole app. They must be class components.
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, info); // log to error service
}
render() {
if (this.state.hasError) return <h2>Something went wrong.</h2>;
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<RiskyComponent />
</ErrorBoundary>
Q35What is React Suspense?Hard▼
Suspense lets components "wait" for something (lazy loading, data fetching) and show a fallback while waiting.
// With lazy loading
<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>
// With React Query / data fetching (React 18+)
<Suspense fallback={<Skeleton />}>
<UserProfile userId={1} /> {/* suspends while fetching */}
</Suspense>
Q36What is the difference between Redux and Context API?Hard▼
- Context API: built-in, good for low-frequency updates (theme, user auth, locale). All consumers re-render on any change.
- Redux: external library, good for complex, high-frequency state. Selective re-renders with selectors. Built-in devtools for time-travel debugging.
When to use Redux: large app with many components sharing complex state, frequent updates, need for middleware (logging, async). Modern alternative: Zustand — simpler than Redux, smaller bundle.
Q37What is lifting state up?Medium▼
When two sibling components need to share state, move the state to their nearest common parent and pass it down as props.
// Before: each has own state — can't sync
function TemperatureC() { const [temp, setTemp] = useState(0); ... }
function TemperatureF() { const [temp, setTemp] = useState(32); ... }
// After: state lifted to parent
function Calculator() {
const [celsius, setCelsius] = useState(0);
const fahrenheit = celsius * 9/5 + 32;
return (
<>
<TemperatureC value={celsius} onChange={setCelsius} />
<TemperatureF value={fahrenheit} onChange={f => setCelsius((f-32)*5/9)} />
</>
);
}
Q38What are higher-order components (HOC) in React?Hard▼
A HOC is a function that takes a component and returns a new enhanced component. Used for cross-cutting concerns (auth, logging, data fetching).
// HOC that adds auth protection
function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" />;
return <WrappedComponent {...props} user={user} />;
};
}
// Usage
const ProtectedDashboard = withAuth(Dashboard);
HOCs are a pattern from before hooks. Custom hooks are now preferred for most use cases.
Q39What is the render props pattern?Hard▼
// Component that shares logic via a render prop
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
const handleMove = e => setPos({ x: e.clientX, y: e.clientY });
return <div onMouseMove={handleMove}>{render(pos)}</div>;
}
// Usage
<MouseTracker render={({ x, y }) => <p>X:{x} Y:{y}</p>} />
Like HOCs, render props are a pre-hooks pattern. Custom hooks now solve the same problem more cleanly.
Q40What is forwardRef?Hard▼
// forwardRef lets a parent pass a ref to a child's DOM element
const FancyInput = forwardRef((props, ref) => {
return <input ref={ref} className="fancy" {...props} />;
});
// Parent can now access the input DOM directly
function Form() {
const inputRef = useRef();
return (
<>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);
}
Q41What is React Portals?Hard▼
Portals render a child into a DOM node outside the parent component's DOM hierarchy — useful for modals, tooltips, and dropdowns.
import { createPortal } from "react-dom";
function Modal({ children, isOpen }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay">{children}</div>,
document.getElementById("modal-root") // outside #app
);
}
// HTML: <div id="app"></div> <div id="modal-root"></div>
Events still bubble up through React tree, not the DOM tree.
Q42How does React handle forms?Medium▼
function RegisterForm() {
const [form, setForm] = useState({ name: "", email: "", password: "" });
const handleChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value })); // computed key!
};
const handleSubmit = async (e) => {
e.preventDefault(); // prevent page reload
await submitForm(form);
};
return (
<form onSubmit={handleSubmit}>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
<input name="password" value={form.password} onChange={handleChange} type="password" />
<button type="submit">Register</button>
</form>
);
}
Q43What is reconciliation in React?Hard▼
Reconciliation is how React updates the DOM efficiently. When state changes, React compares the new Virtual DOM with the previous one (diffing) and updates only what changed.
React's diffing heuristics:
- Elements of different types → destroy old tree, build new tree
- Same element type → update changed attributes only
- Lists → use keys to match old/new items
This is O(n) complexity thanks to the heuristics (vs O(n³) for a naive algorithm).
Q44What is React 18's Concurrent Mode?Hard▼
Concurrent Mode lets React work on multiple state updates at the same time, interrupt rendering if something more urgent comes in, and resume later. This keeps the UI responsive.
Key features added in React 18:
- Automatic batching — multiple state updates batched into one render (even in setTimeout, Promises)
useTransition— mark updates as low-priorityuseDeferredValue— defer a value updatestartTransition— outside of components
// Enable with ReactDOM.createRoot (React 18+)
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
Q45What are React Server Components?Hard▼
React Server Components (RSC) run on the server, have zero JavaScript sent to the client. They can directly access databases/filesystems. Used heavily in Next.js 13+.
// Server Component (default in Next.js App Router)
async function UserList() {
const users = await db.query("SELECT * FROM users"); // direct DB access!
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// Client Component — needs interactivity
"use client";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c + 1)}>{count}</button>;
}
Q46What is the difference between useEffect and useLayoutEffect — which runs first?Hard▼
Order of execution after a render:
- React renders (runs your component function)
- React commits to the DOM
useLayoutEffectfires (synchronous, before paint)- Browser paints the screen
useEffectfires (asynchronous, after paint)
Use useLayoutEffect for measuring DOM elements before the user sees them. Use useEffect for everything else.
Q47What are some React performance optimization techniques?Medium▼
- React.memo — prevent re-renders when props haven't changed
- useMemo — memoize expensive computed values
- useCallback — stable function references for memoized children
- Code splitting with
React.lazy()— smaller initial bundle - Virtualization — render only visible list items (react-window, react-virtual)
- useTransition — keep UI responsive during heavy updates
- Avoid unnecessary state — derive values instead of storing them
- Avoid anonymous functions in JSX —
onClick={() => fn()}creates new function each render
Q48What is the children prop?Easy▼
// children is whatever you put between component tags
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Usage
<Card title="Welcome">
<p>This paragraph is the children prop.</p>
<button>Click me</button>
</Card>
Q49What is batching in React 18?Hard▼
// React 17 — only batched inside event handlers
setTimeout(() => {
setCount(c => c + 1); // triggers render
setFlag(f => !f); // triggers another render
// 2 renders
}, 0);
// React 18 — batches everywhere (Automatic Batching)
setTimeout(() => {
setCount(c => c + 1); // batched
setFlag(f => !f); // batched
// 1 render
}, 0);
Automatic batching means fewer re-renders. Use flushSync() from react-dom if you need to opt out.
Q50What is Next.js and how is it related to React?Medium▼
Next.js is a React framework built on top of React that adds:
- File-based routing — no React Router needed
- Server-side rendering (SSR) — better SEO
- Static site generation (SSG) — very fast pages
- API routes — backend endpoints inside the same project
- Image optimization, font optimization, edge runtime
React is a UI library. Next.js is a full-stack framework using React for the UI layer. Most React jobs in India now expect Next.js knowledge too.