React Application Structure
Entry point, root render, and where a component tree begins.
What you will be able to do
- Name the files a Vite React app starts from, and the order they run in
- Explain what the empty <div id="root"> in index.html is for
- Read main.tsx line by line - StrictMode, createRoot, and render
- Explain what App.tsx is for, and why it is separate from main.tsx
- Describe how the component tree grows from App
- Recognise what JSX turns into before React sees it
- Diagnose the two classic startup mistakes: a missing root, and a root never rendered
The idea, in plain English
Lessons 1 and 2 were about what React is. This one is about where it starts. Every React app begins the same way: the browser loads a plain HTML page with an empty box in it, a small script finds that box, and React takes it over and fills it with your components.
Three files carry that story. index.html provides the empty box - <div id="root"></div>. main.tsx is the entry point: it finds the box, turns it into a React root with createRoot, and renders <App /> into it. App.tsx holds the App component, the top of your component tree.
The split between the last two is deliberate. main.tsx starts React and mounts the app, and you rarely touch it again. App.tsx describes what the app is, and it grows with every feature. Keeping "start the engine" apart from "the thing the engine runs" is why the two files exist.
From App downwards it is components all the way: App renders a Header, a CourseList, and a Footer; CourseList renders CourseCards; and so on. Once you can trace index.html to main.tsx to App to the tree, every React project you open will make sense at a glance.
Worked example: Reading main.tsx and App.tsx line by line.
index.html and the empty root
A Vite React app still starts from one real HTML file. Most of it is ordinary: a charset, a viewport, a title. The two lines that matter are <div id="root"></div> and the script tag that loads /src/main.tsx.
The root div is deliberately empty. It is the container React will render into - everything your components produce ends up inside it. Nothing else on the page is managed by React.
The script has type="module", so the browser runs main.tsx as an ES module. During development Vite translates the TypeScript and JSX on the fly; in a production build the tag is rewritten to point at a bundled, hashed file - Lesson 4 looks inside that build.
main.tsx, line by line
The imports come first. StrictMode is a development helper from react. createRoot comes from react-dom/client - react is the component model, react-dom is the part that talks to the browser. App is your own component, imported from ./App.
document.getElementById("root") finds the empty div. createRoot(...) turns that element into a React root - the place React now manages. .render(<App />) tells React what to show inside it.
StrictMode wraps App to surface problems early. In development it deliberately renders each component twice, which flushes out code that relies on running exactly once. In a production build it does nothing at all.
import { StrictMode } from "react"A development-only checker. Renders components twice in development; no effect in production.import { createRoot } from "react-dom/client"The browser-side API for starting React in a DOM element.import App from "./App"Your top-level component - the start of the tree.document.getElementById("root")Finds the empty <div id="root"> from index.html.createRoot(element)Makes that element a React root that React will manage..render(<App />)Renders App into the root. Without this call the page stays empty.Watch out: The ! in getElementById("root")! is TypeScript saying "trust me, this is not null". If the div is missing, TypeScript stays quiet and React throws "Target container is not a DOM element." - a clearer check is shown in the code below.
App.tsx and where the tree begins
App is an ordinary component - a function that returns JSX - that happens to sit at the top. Everything else is rendered by App or by something App renders.
A real App is usually short: it arranges a few large pieces - a Header, a main area, a Footer - and each of those renders its own children. The tree mirrors the page, top to bottom.
The name App is a convention, not a rule. Call it LearningPlatform and render <LearningPlatform /> in main.tsx, and nothing changes. Most projects keep App so everyone knows where to look.
main.tsx against App.tsx
One rule covers it: main.tsx starts React; App.tsx defines the UI. main.tsx runs once, is a handful of lines, and changes only for app-wide setup such as a router or a data provider. App.tsx is the application itself.
Keeping them separate means the startup code never gets tangled with the UI, and App can be imported on its own - by a test, for example - without starting anything.
main.tsxThe entry point. Finds #root, calls createRoot, renders <App />.App.tsxThe top component. Returns JSX and composes the rest of the UI.Runsmain.tsx once at startup; App on every render.Changes whenmain.tsx: app-wide setup is added. App.tsx: the UI changes.What React actually renders
Browsers do not understand JSX. Before your code runs, a compiler rewrites every JSX tag into an ordinary JavaScript function call that describes the element. React runs those calls, builds a description of the UI, and then creates the matching DOM inside #root.
Older setups compiled <h1>Hello React</h1> to React.createElement("h1", null, "Hello React"), which is why files once had to import React. The modern JSX transform compiles it to a jsx() call imported automatically from react/jsx-runtime, so that import is no longer needed. Either way, JSX is JavaScript underneath.
That is also why JSX is not HTML. It looks similar, but it lives inside JavaScript, so {name} can drop a JavaScript value straight into the markup. Lesson 5 covers JSX properly.
Syntax and examples
my-react-app/
├── public/
├── src/
│ ├── assets/
│ ├── App.tsx the top component - where the tree begins
│ └── main.tsx the entry point - starts React
├── index.html the page, with an empty <div id="root">
├── package.json
├── tsconfig.json
└── vite.config.ts<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React App</title>
</head>
<body>
<!-- Empty on purpose: React renders the whole app in here. -->
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);function App() {
return (
<div>
<h1>My Learning Platform</h1>
<p>Learn React step by step.</p>
</div>
);
}
export default App;Browser requests /
↓
index.html the page, with an empty <div id="root">
↓
main.tsx loaded by the module script tag
↓
getElementById finds #root
↓
createRoot() makes it a React root
↓
render(<App />) starts rendering
↓
App renders Header, main, Footer ...
↓
DOM the result appears inside #rootimport { createRoot } from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
// A plain error beats "Target container is not a DOM element."
if (!rootElement) {
throw new Error('Root element not found');
}
createRoot(rootElement).render(<App />);import Header from './components/Header';
import CourseList from './components/CourseList';
import Footer from './components/Footer';
function App() {
return (
<>
<Header />
<main>
<CourseList />
</main>
<Footer />
</>
);
}
export default App;
// App
// ├── Header
// ├── main
// │ └── CourseList
// └── Footer// <h1>Hello React</h1>, compiled the old way:
function App() {
return React.createElement("h1", null, "Hello React");
}// The same component, compiled the way Vite does it today.
// The import is added for you - which is why you no longer import React.
import { jsx as _jsx } from "react/jsx-runtime";
function App() {
return _jsx("h1", { children: "Hello React" });
}src/
├── components/ Header.tsx, Footer.tsx, CourseCard.tsx
├── pages/ Home.tsx, Courses.tsx, CourseDetails.tsx
├── hooks/ reusable stateful logic (Module 5)
├── services/ API calls (Module 6)
├── store/ global state (Module 8)
├── types/ shared TypeScript types (Module 12)
├── App.tsx
└── main.tsxTip: .jsx is JavaScript with JSX; .tsx is TypeScript with JSX. This course uses .tsx from the start, so the TypeScript module later feels like a continuation rather than a switch.
Watch out: Seeing a component render twice in development is StrictMode doing its job, not a bug. It checks your code for side effects; a production build renders once.
The startup files
In the order the browser meets them.
index.htmlThe page itself. Holds the empty root and loads main.tsx.
<div id="root"></div>
#rootThe DOM element React renders the whole app into.
main.tsxThe entry point that starts React.
createRoot(el).render(<App />)
createRootTurns a DOM element into a React root. From react-dom/client.
renderTells React what to show in the root.
StrictModeDevelopment-only checks; double renders to expose side effects.
<StrictMode><App /></StrictMode>
App.tsxThe top-level component, where the component tree begins.
.tsxA TypeScript file that may contain JSX.
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“In a new Vite app, rename the div in index.html to id="app" and reload. Read the error in the console, then change main.tsx to the explicit check shown above and compare the message.”
“Delete .render(<App />) so only createRoot(...) remains. The page goes blank with no error at all - a quieter failure than the missing root.”
“Add console.log("App rendered") to App. In development you will see it twice; remove StrictMode, or build for production, and it appears once.”
What usually goes wrong
main.tsx looks for id="root". If index.html no longer has it, React has nowhere to render and throws "Target container is not a DOM element." - and the ! in getElementById("root")! hides the null from TypeScript.
✗ <!-- index.html -->
<div id="app"></div>
// main.tsx
createRoot(document.getElementById('root')!)✓ <!-- index.html -->
<div id="root"></div>
// main.tsx
createRoot(document.getElementById('root')!)createRoot only prepares the container. Without .render, the page stays blank and nothing reports an error.
✗ createRoot(rootElement);✓ createRoot(rootElement).render(<App />);App is your component, imported from your own file. createRoot comes from the react-dom/client package. They live in different places and do different jobs.
✗ import { App } from 'react';✓ import App from './App';
import { createRoot } from 'react-dom/client';A one-line App is fine for a demo. As features arrive, App should compose other components rather than contain them.
In development, StrictMode renders each component twice on purpose. Removing StrictMode to silence it hides exactly the problems it exists to catch.
Best practices
- Keep main.tsx small: find the root, create it, render App - plus app-wide providers when they arrive.
- Check for the root element explicitly, so a missing root fails with a clear message.
- Keep StrictMode on in development.
- Let App compose large sections; put each section in its own component under src/components.
- Keep the name App unless there is a strong reason not to - it is where everyone looks first.
Practice
Write these yourself before opening anything. Getting them wrong first is most of how this sticks.
Rename the top component to LearningPlatform and render it from main.tsx.
Show hintHide hint
Only two things change: the function name and the component rendered in main.tsx.
Show solutionHide solution
// LearningPlatform.tsx
function LearningPlatform() {
return <h1>Learning Platform</h1>;
}
export default LearningPlatform;
// main.tsx
import { createRoot } from 'react-dom/client';
import LearningPlatform from './LearningPlatform';
createRoot(document.getElementById('root')!).render(<LearningPlatform />);Rewrite main.tsx so that a missing root element throws "Root element not found" instead of relying on the ! operator.
Show hintHide hint
Store the element, check it, then pass it to createRoot.
Show solutionHide solution
import { createRoot } from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element not found');
}
createRoot(rootElement).render(<App />);Without looking back, explain in your own words how the app gets from index.html to what appears on screen.
Show hintHide hint
Walk the chain: the page, the empty box, the entry script, the root, the first component, the tree.
Show solutionHide solution
The browser loads index.html, which contains an empty
<div id="root"> and a module script tag for main.tsx.
main.tsx finds that div, turns it into a React root with
createRoot, and renders <App /> into it.
App is the top component. It renders the big sections -
Header, main, Footer - and each of those renders its own
children. That tree of components is turned into real DOM
inside #root, and that is what appears on screen.A three-part learning platform
Build the startup files and three components for a small learning platform page, keeping main.tsx and App.tsx to their separate jobs.
- main.tsx finds #root with an explicit check and renders App.
- Header.tsx shows "My Learning Platform".
- CourseList.tsx shows a "Courses" heading and a list of React, Node.js, Python, and TypeScript.
- Footer.tsx shows "© 2026 My Learning Platform".
- App.tsx composes Header, CourseList inside <main>, and Footer - and contains nothing else.
src/
├── components/
│ ├── Header.tsx
│ ├── CourseList.tsx
│ └── Footer.tsx
├── App.tsx
└── main.tsxShow one solutionHide solution
// src/components/Header.tsx
function Header() {
return (
<header>
<h1>My Learning Platform</h1>
</header>
);
}
export default Header;
// src/components/CourseList.tsx
const courses = ['React', 'Node.js', 'Python', 'TypeScript'];
function CourseList() {
return (
<section>
<h2>Courses</h2>
<ul>
{courses.map((course) => (
<li key={course}>{course}</li>
))}
</ul>
</section>
);
}
export default CourseList;
// src/components/Footer.tsx
function Footer() {
return <footer>© 2026 My Learning Platform</footer>;
}
export default Footer;
// src/App.tsx - composes, nothing more
import Header from './components/Header';
import CourseList from './components/CourseList';
import Footer from './components/Footer';
function App() {
return (
<>
<Header />
<main>
<CourseList />
</main>
<Footer />
</>
);
}
export default App;
// src/main.tsx - starts React, nothing more
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element not found');
}
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>,
);Key points
- index.html provides an empty <div id="root"> - the container for the whole app.
- main.tsx is the entry point: it finds #root, calls createRoot, and renders <App />.
- createRoot turns a DOM element into a React root; render tells React what to show in it.
- Forget render and the page stays blank with no error.
- A missing root throws "Target container is not a DOM element." unless you check for it explicitly.
- App is the top component and the start of the component tree; the name is a convention.
- main.tsx starts React; App.tsx defines the UI.
- JSX compiles to plain JavaScript calls before React ever sees it.
- StrictMode renders components twice in development only, to expose side effects.
Quick check before you move on
Interview questions
What is the entry point of a React application?
In a Vite app, main.tsx. It finds the root element, creates a React root with createRoot, and renders the top-level App component into it.
What does createRoot() do?
It creates a React root for a DOM container, so React can manage and render into that element. Rendering itself happens when you call render on the root.
What is the purpose of App.tsx?
It holds the top-level component, where the component tree begins. It usually composes the main sections of the UI rather than containing them.
What is <div id="root"></div> for?
It is the DOM container React mounts the app into. It starts empty, and everything React renders appears inside it.
What is the difference between main.tsx and App.tsx?
main.tsx bootstraps React and mounts the app once at startup. App.tsx defines the UI and changes as the app grows. Keeping them apart keeps startup code out of the UI and lets App be imported on its own, for example in tests.
What is StrictMode?
A development-only wrapper that adds extra checks, including rendering components twice to surface side effects. It has no effect in production builds.
What is a .tsx file?
A TypeScript file that can contain JSX. .jsx is the JavaScript equivalent.
Quiz
- 1.
Which file commonly bootstraps a Vite React application? A. index.css B. main.tsx C. package.json D. README.md
- 2.
What does createRoot(rootElement).render(<App />) do? A. Creates a database B. Starts a Node server C. Mounts the React application D. Compiles CSS
- 3.
Where does React usually mount in a basic Vite app? <div id="______"></div> A. app B. main C. root D. react
- 4.
What is <App />? A. A React component B. A database C. A CSS class D. An HTML attribute
- 5.
Which statement is correct? A. main.tsx normally defines every UI component B. App.tsx creates the database C. main.tsx commonly mounts the root component D. index.html replaces React
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned