Build Websites and Apps with AI
ProjectBeginner 30 min· Project: coffee shop website · 7 of 21

Start the React project with Vite

Create the Brew & Bean project with Vite and React, add react-router-dom, and build the site shell: header, footer and four routed pages. You will test it, learn what each starter file does, and save your first Git commit.

In this lesson you will create the Brew & Bean project with Vite and React, add page routing, and build the shell of the site: a header with links, a footer and four empty pages. At the end you can click between Home, Menu and Cart and see the address bar change, without the page reloading. The next lessons fill those pages with real content.

You need: Node.js (Vite needs version 20.19 or newer, or 22.12 or newer; check with node -v), VS Code, Git and the SPEC.md you planned in *Coffee shop website: plan the site with AI*. If you haven't set up your machine yet, do *Set up your machine: Node.js, VS Code, Git and a free AI assistant* first.

What is Vite and why use it

React is a library for building screens from components. To run it in a browser you also need a build tool: something that turns your JSX files into plain JavaScript, starts a local server and refreshes the page when you save. Vite (pronounced "veet") is the standard choice today. It starts quickly, and npm run build produces the small static files you will deploy in the last lesson.

If an AI or an old tutorial tells you to use create-react-app, don't. It is no longer maintained and Vite replaced it for new projects.

Ask AI for the setup, then check it

Getting the first commands right is a good job for AI, and a good place to practise checking its answer.

Prompt · Ask for the setup steps
I am a beginner building the website described in this spec:

<spec>
[paste SPEC.md here]
</spec>

Give me the exact terminal commands to create the project with Vite (React, JavaScript, not TypeScript), install react-router-dom, and start the dev server. I am on [Windows / macOS / Linux]. Then list the folders I should create inside src for this spec. Do not write any component code yet. Explain each command in one line.

What to expect: Check: it uses npm create vite@latest (not create-react-app), picks the React JavaScript template, installs react-router-dom, and starts with npm run dev. If the answer includes TypeScript, Tailwind or Redux, reply "not in the spec, remove it".

Create the project

Create and run

  1. 1Open a terminal in the folder where you keep your projects (not inside another project).
  2. 2Run the create command below. When Vite asks questions, choose React as the framework and JavaScript as the variant. If it asks about extra options (such as an experimental bundler or starting the dev server), say no; the project in this lesson was tested with the defaults.
  3. 3Move into the new folder and install the packages. Installing takes a minute and downloads the node_modules folder.
  4. 4Install the routing library. This adds react-router-dom to package.json.
  5. 5Start the dev server and open the address it prints.
Terminal
npm create vite@latest brew-and-bean -- --template react
cd brew-and-bean
npm install
npm install react-router-dom
npm run dev

The same commands work in PowerShell, Command Prompt and macOS or Linux terminals.

What the terminal prints (your version number may differ)
  VITE v8.x  ready in 300 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

Open http://localhost:5173/ in your browser. You will see the Vite and React starter page. Keep the terminal running while you work: every time you save a file the browser updates by itself. Stop the server with Ctrl+C.

A tour of the files

What Vite created

File or folderWhat it is for
index.htmlThe one HTML page. React puts the whole site inside <div id="root">
src/main.jsxThe entry point. It starts React and mounts the App component
src/App.jsxThe top component. We will make it the page layout and routes
src/index.cssGlobal styles for the whole site
public/Files copied as they are (for example the favicon)
package.jsonProject name, scripts (dev, build, preview) and dependencies
node_modules/Downloaded packages. Never edit it and never commit it
vite.config.jsVite settings. We keep the default

The template may look a little different from this table, because Vite updates it often. The ideas are the same.

Clean out the starter code

The starter page has a counter, logos and styles we do not need. Delete them so that nothing confusing is left behind. In the terminal (stop the server with Ctrl+C first, or open a second terminal):

Terminal (macOS, Linux or Git Bash)
# macOS / Linux / Git Bash
rm -rf src/assets src/App.css public/icons.svg
mkdir -p src/components src/pages src/context src/data src/utils

If a file in the list does not exist in your template, ignore the error and carry on.

Terminal (PowerShell)
# PowerShell
Remove-Item -Recurse -Force src/assets, src/App.css, public/icons.svg -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force src/components, src/pages, src/context, src/data, src/utils

Does the same as the Bash version.

Making files in VS Code

In VS Code you can also create folders by right-clicking a folder in the Explorer, or by creating a file and typing the path, for example src/pages/Home.jsx. VS Code creates the missing folders for you.

Build the shell

We now write the files. First, the page title in index.html. The browser tab will show "Brew & Bean" instead of "brew-and-bean". Note that the ampersand is written as &amp; in HTML.

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Brew &amp; Bean</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

Only the title changed from the starter. The favicon stays for now; we replace it in the polish lesson.

Next, src/main.jsx. This is where we wrap the app in BrowserRouter, which lets React read and change the address bar. In the cart lesson we will wrap it in one more provider.

src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.jsx'
import './index.css'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>,
)

StrictMode asks React to warn you about common mistakes while you develop. It does nothing in the built site.

Now src/App.jsx: the layout that every page shares (header at the top, footer at the bottom) with a Routes block that swaps the page in the middle. path="*" matches any address that no other route matched, which is how we make the "not found" page.

src/App.jsx
import { Routes, Route } from 'react-router-dom'
import Header from './components/Header.jsx'
import Footer from './components/Footer.jsx'
import Home from './pages/Home.jsx'
import Menu from './pages/Menu.jsx'
import Cart from './pages/Cart.jsx'
import NotFound from './pages/NotFound.jsx'

export default function App() {
  return (
    <div className="app">
      <Header />
      <main id="main">
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/menu" element={<Menu />} />
          <Route path="/cart" element={<Cart />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </main>
      <Footer />
    </div>
  )
}

The main element has an id so that, in the polish lesson, a skip link can jump to it.

The header uses NavLink instead of a normal <a>. A normal link makes the browser reload the whole page. NavLink changes the page inside React, which is faster, and it adds the class active (and aria-current="page") to the link of the current page. The end prop on the Home link stops it from counting as active on every address that starts with /.

src/components/Header.jsx
import { Link, NavLink } from 'react-router-dom'

export default function Header() {
  return (
    <header className="site-header">
      <div className="container header-inner">
        <Link to="/" className="brand">
          Brew &amp; Bean
        </Link>
        <nav aria-label="Main">
          <ul className="nav-list">
            <li>
              <NavLink to="/" end>
                Home
              </NavLink>
            </li>
            <li>
              <NavLink to="/menu">Menu</NavLink>
            </li>
            <li>
              <NavLink to="/cart">Cart</NavLink>
            </li>
          </ul>
        </nav>
      </div>
    </header>
  )
}
src/components/Footer.jsx
const year = new Date().getFullYear()

export default function Footer() {
  return (
    <footer className="site-footer">
      <div className="container">
        <p>
          <strong>Brew &amp; Bean</strong>, Indiranagar, Bengaluru
        </p>
        <p>Open 8 am to 10 pm. Pickup orders only.</p>
        <p>&copy; {year} Brew &amp; Bean</p>
      </div>
    </footer>
  )
}

The year is calculated once, when the file loads, so it stays correct.

Now four small placeholder pages. We will replace Home and Menu in the next lesson and Cart in the one after.

src/pages/Home.jsx
export default function Home() {
  return (
    <section className="container page">
      <h1>Brew &amp; Bean</h1>
      <p>Home page coming soon.</p>
    </section>
  )
}
src/pages/Menu.jsx
export default function Menu() {
  return (
    <section className="container page">
      <h1>Our menu</h1>
      <p>Menu page coming soon.</p>
    </section>
  )
}
src/pages/Cart.jsx
export default function Cart() {
  return (
    <section className="container page">
      <h1>Your cart</h1>
      <p>Cart page coming soon.</p>
    </section>
  )
}
src/pages/NotFound.jsx
import { Link } from 'react-router-dom'

export default function NotFound() {
  return (
    <section className="container page">
      <h1>Page not found</h1>
      <p>We could not find that page.</p>
      <Link to="/">Go back home</Link>
    </section>
  )
}

A normal Link is used for the way home. Routing links should always be Link or NavLink, never a plain a tag.

Finally, replace everything in src/index.css with the base styles below. The colours are variables, so we can change the look of the whole site from one place. Text on the cream background has plenty of contrast, which matters for accessibility later.

src/index.css
:root {
  --bg: #faf6f1;
  --surface: #ffffff;
  --text: #2b1d14;
  --muted: #6b5b50;
  --brand: #6f4e37;
  --brand-dark: #4e3626;
  --accent: #f3e9df;
  --border: #e4d6c8;
  --field-border: #8a7668;
  --danger: #b00020;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
}

h1,
h2,
h3 {
  line-height: 1.2;
}

a {
  color: var(--brand);
}

.container {
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 24px;
}

.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

.page {
  padding-top: 32px;
  padding-bottom: 32px;
}

/* Header */
.site-header {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.header-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 16px;
  padding-bottom: 16px;
}

.brand {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--brand);
  text-decoration: none;
}

.nav-list {
  list-style: none;
  display: flex;
  gap: 24px;
  margin: 0;
  padding: 0;
}

.nav-list a {
  color: var(--text);
  text-decoration: none;
  padding: 4px 0;
  border-bottom: 2px solid transparent;
}

.nav-list a:hover,
.nav-list a.active {
  border-bottom-color: var(--brand);
}

/* Footer */
.site-footer {
  background: var(--text);
  color: var(--accent);
  padding: 24px 0;
  margin-top: 48px;
}

.site-footer p {
  margin: 4px 0;
}

Later lessons add more rules at the end of this file.

Test it

What to check

  1. 1Start the server again with npm run dev and open the address.
  2. 2You should see a white header with the logo on the left and Home, Menu and Cart links on the right, and a dark footer at the bottom of the page.
  3. 3Click Menu, then Cart, then Home. The text in the middle changes, the address bar changes, and the page does not flash or reload.
  4. 4Type http://localhost:5173/menu straight into the address bar and press Enter. The Menu page should load.
  5. 5Type http://localhost:5173/coffee. You should see "Page not found" and a link back.
  6. 6Check the browser tab says "Brew & Bean".
What you should see
Home page:  "Brew & Bean" and "Home page coming soon."
/menu:      "Our menu" and "Menu page coming soon."
/cart:      "Your cart" and "Cart page coming soon."
/coffee:    "Page not found" with a link "Go back home"

If something goes wrong

Common problems

ProblemLikely causeFix
npm or node is not recognisedNode.js is not installed or the terminal was opened before installing itInstall Node.js, close and reopen the terminal, run node -v
Blank white pageA JavaScript errorOpen the browser console (F12, Console tab) and read the first red message
Failed to resolve import './components/Header.jsx'Wrong file name, folder or capital lettersCheck the file is at src/components/Header.jsx and the spelling matches exactly
Port 5173 is in useAnother dev server is still runningClose the other terminal, or use the other address Vite prints
Page not found on /menu after you deployThe host needs a rule for single-page appsCovered in the deploy lesson
Prompt · Ask AI to explain an error
I am building a React app with Vite and react-router-dom. I got this error:

<error>
[paste the full error from the terminal or browser console]
</error>

This is the file it points to:

<code>
[paste the file]
</code>

Explain in plain English what the error means and what caused it. Then give the smallest fix. Do not rewrite the whole file.

What to expect: A good answer names the exact line, explains the cause, and changes only what is needed. If the AI rewrites the whole file, ask again for the smallest fix.

Save your work with Git

Make your first commit now. If the next lesson goes wrong you can always return to this point. Save your spec as SPEC.md in the project folder first (the same level as package.json).

Terminal
git init
git add .
git commit -m "Set up Vite, routing and layout"

Vite's .gitignore already excludes node_modules and dist, so only your own files are committed.

Right folder

Run Git commands from inside the brew-and-bean folder. If you run git init in a parent folder such as your Documents, Git will try to track everything inside it.

Try it: Add an About page

Add a fifth page at /about that says who runs the shop and why you made the site. Create src/pages/About.jsx, add a route in App.jsx and a link in the header.

Try it: Stretch: break it on purpose

Rename Header.jsx to header.jsx and read the error. Then rename it back. Ask your AI: "Why does file name capitalisation matter here?"

Key takeaways

  1. Vite creates and runs React projects: npm create vite@latest, npm install, npm run dev.
  2. Don't use create-react-app for new projects; it is no longer maintained.
  3. BrowserRouter, Routes and Route show a different page for each address; NavLink and Link change pages without a reload.
  4. A shared layout (header, main, footer) lives in App.jsx; each page is its own component in src/pages.
  5. Test by clicking and by typing addresses directly, including one that should not exist.
  6. Commit after each working step so you can always go back.

Check yourself

Finishing the quiz marks the lesson as done.

1. Why does the header use NavLink instead of a plain a tag?

2. What does the route path="*" do?

3. You see a blank white page after editing a file. What is the best first step?

4. What should you do about node_modules when using Git?

5. What does the end prop do on <NavLink to="/" end>?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free