Setting Up React App Routing Using A Material UI Framework

A navigation bar can look polished and still leave an application frustrating to use. If clicking a button reloads the page, refreshing a bookmarked URL produces an error, or the current section is impossible to identify, the problem is not simply visual. It is how navigation and routing work together.
React Router and Material-UI solve different parts of that problem. React Router connects URLs to components; Material-UI supplies the app bar, buttons, layout, and typography around them. Used together, they let you build an application that feels consistent while preserving familiar browser behavior.
This tutorial uses the 2019-era React Router v5 and Material-UI v4 APIs; tools and interfaces may have changed since then.
We will build a small application with Home, Projects, and About pages, a project-detail route, active navigation, and a not-found screen. We will also cover the deployment setting that makes direct links work.
How routing and Material-UI fit together
In a traditional multi-page website, following a link generally requests another HTML document from the server. In a React single-page application, the browser loads the application shell first. Client-side routing then changes the rendered page as the URL changes, without requesting a new document for every internal navigation.
That does not mean URLs become unimportant. Good routes should support bookmarks, browser Back and Forward, and links shared outside the application.
Material-UI is a React component library implementing Material Design. It does not decide which page belongs at /about. React Router handles that mapping; Material-UI provides the interface through which visitors reach it.
Our application will have three layers:
- Router: reads and updates browser history.
- Persistent layout: holds navigation and the main content area.
- Page components: render according to the current route.
Keeping these responsibilities separate makes navigation easier to extend and debug.
1. Create the application and install compatible packages
Start with a working Node.js and npm installation. From your terminal, create the project:
npx create-react-app routing-demo
cd routing-demo
npm install @material-ui/core@4 react-router-dom@5
npm start
The major versions matter. This example uses Material-UI’s @material-ui/core package and React Router’s Switch, Route, and NavLink APIs. Examples written for different major versions may not be interchangeable.
Create React App supplies the development server, JSX compilation, and production build configuration. Material-UI’s core package includes everything needed for this tutorial; no icon package is required.
Use this small file structure:
src/
index.js
App.js
pages.js
Keeping the pages together makes the example easy to follow. In a larger application, move each page into its own file and group related screens by feature.
2. Create the routed pages
Add the following to src/pages.js:
import React from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Button, Typography } from '@material-ui/core';
export function Home() {
return (
<>
<Typography variant="h4" component="h1" gutterBottom>
Home
</Typography>
<Typography paragraph>
Welcome. Explore the projects or learn about this application.
</Typography>
<Button
component={RouterLink}
to="/projects"
variant="contained"
color="primary"
>
Browse projects
</Button>
</>
);
}
export function Projects() {
return (
<>
<Typography variant="h4" component="h1" gutterBottom>
Projects
</Typography>
<Typography paragraph>
Choose a project to view its details.
</Typography>
<Button component={RouterLink} to="/projects/website">
Website project
</Button>
</>
);
}
export function ProjectDetail({ match }) {
return (
<>
<Typography variant="h4" component="h1" gutterBottom>
Project details
</Typography>
<Typography paragraph>
Project identifier: {match.params.projectId}
</Typography>
<Button component={RouterLink} to="/projects">
Back to projects
</Button>
</>
);
}
export function About() {
return (
<>
<Typography variant="h4" component="h1" gutterBottom>
About
</Typography>
<Typography paragraph>
This example combines React Router with Material-UI navigation.
</Typography>
</>
);
}
export function NotFound() {
return (
<>
<Typography variant="h4" component="h1" gutterBottom>
Page not found
</Typography>
<Typography paragraph>
Check the address or return to the home page.
</Typography>
<Button component={RouterLink} to="/">
Return home
</Button>
</>
);
}
Notice the difference between variant and component on Typography. The variant controls appearance; component="h1" provides the document heading. A page heading can therefore have a moderate visual size without losing its semantic meaning.
The detail page receives a match object from React Router. Later, we will define :projectId as a dynamic URL segment. For /projects/website, match.params.projectId will be website.
This example simply displays that identifier. A real detail page should use it to locate data and handle loading, request failures, and missing records.
3. Build the persistent layout and route table
Replace src/App.js with:
import React from 'react';
import {
BrowserRouter,
NavLink,
Route,
Switch
} from 'react-router-dom';
import {
AppBar,
Button,
Container,
CssBaseline,
Toolbar,
Typography
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import {
Home,
Projects,
ProjectDetail,
About,
NotFound
} from './pages';
const useStyles = makeStyles(theme => ({
toolbar: {
flexWrap: 'wrap'
},
brand: {
flexGrow: 1,
marginRight: theme.spacing(2)
},
navigation: {
display: 'flex',
flexWrap: 'wrap'
},
navigationLink: {
margin: theme.spacing(0.5),
'&.active': {
backgroundColor: 'rgba(255, 255, 255, 0.18)',
textDecoration: 'underline'
}
},
content: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4)
}
}));
export default function App() {
const classes = useStyles();
return (
<BrowserRouter>
<CssBaseline />
<AppBar position="static">
<Toolbar className={classes.toolbar}>
<Typography variant="h6" className={classes.brand}>
Routing demo
</Typography>
<nav
aria-label="Main navigation"
className={classes.navigation}
>
<Button
component={NavLink}
exact
to="/"
color="inherit"
className={classes.navigationLink}
activeClassName="active"
>
Home
</Button>
<Button
component={NavLink}
to="/projects"
color="inherit"
className={classes.navigationLink}
activeClassName="active"
>
Projects
</Button>
<Button
component={NavLink}
to="/about"
color="inherit"
className={classes.navigationLink}
activeClassName="active"
>
About
</Button>
</nav>
</Toolbar>
</AppBar>
<Container
component="main"
maxWidth="md"
className={classes.content}
>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/projects" component={Projects} />
<Route
exact
path="/projects/:projectId"
component={ProjectDetail}
/>
<Route exact path="/about" component={About} />
<Route component={NotFound} />
</Switch>
</Container>
</BrowserRouter>
);
}
Keep the generated src/index.js rendering <App />. Do not wrap it in another BrowserRouter: this application already has its router at the top of App.
Why route order and exact matching matter
In React Router v5, Switch renders the first matching route. Without exact, the root path / also matches URLs such as /about, potentially showing Home instead of the intended page.
The projects index is also exact so it does not swallow /projects/website. The final route has no path and acts as the fallback after all known routes have been checked.
The app bar sits outside Switch, so it remains visible while the page content changes. This is the simplest pattern for sharing navigation across an application.
4. Connect Material-UI navigation to browser history
The important integration is component={NavLink} or component={RouterLink}. Material-UI’s Button keeps its styling while delegating link behavior to React Router.
Use NavLink when a navigation item needs an active state. Use Link, imported here as RouterLink, for ordinary internal links.
Preserve link semantics
An internal navigation control should behave like a link, not merely resemble one. Using the router link component produces an anchor with a destination and preserves browser features such as opening a page in another tab.
Avoid replacing every navigation action with an onClick handler that calls history methods. Programmatic navigation is useful after events such as successful form submission, but ordinary destinations should remain links.
Likewise, using only href="/about" on a Material-UI button triggers normal document navigation rather than client-side routing. For external destinations, however, a normal href is appropriate.
Make the current section clear
Home uses exact active matching; otherwise it would appear selected on every page. Projects intentionally does not, so it remains highlighted on project-detail screens.
The active style includes an underline as well as a background change. This avoids relying solely on color to communicate location. NavLink also supplies aria-current="page" when active.
For a larger menu, a Material-UI Drawer with list items can use the same component integration. On narrow screens, close a temporary drawer after navigation and test its keyboard behavior. Our three-link menu simply wraps rather than introducing another interaction.
5. Test the navigation as a user would
Do more than click each menu item once. Verify these behaviors:
- Open
/and follow the Browse projects link. - Open the website project and confirm the identifier appears.
- Check that Projects remains highlighted on the detail page.
- Use browser Back and Forward to revisit previous screens.
- Enter
/missing-pageand confirm the fallback renders. - Navigate using Tab and Enter, checking visible focus indicators.
- Test a narrow viewport for overflow and awkward wrapping.
- Paste
/projects/websiteinto a new tab and refresh it.
Client-side navigation does not automatically provide every accessibility behavior of a full document load. Before shipping, update the document title for each screen and establish a deliberate focus or route-announcement strategy. For example, focusing the new page heading after navigation can help keyboard and screen-reader users understand what changed.
6. Configure production hosting for direct links
Build the application with:
npm run build
Deploy the generated build directory to your host. With BrowserRouter, the server must return the application’s index.html for client-side routes that do not correspond to real files.
Otherwise, clicking Projects may work while refreshing /projects returns a server-generated 404. During refresh, the browser requests that URL directly; React Router cannot help until the application has loaded.
For an Nginx site hosted at the domain root, the essential fallback can look like this:
location / {
try_files $uri $uri/ /index.html;
}
Configure the document root separately, and keep API endpoints outside this fallback. Static hosting services usually offer equivalent rewrite rules.
If hosting under a subdirectory, coordinate the build’s asset paths with a router basename, such as <BrowserRouter basename="/demo">. Neither setting replaces the other.
When server rewrites are unavailable, HashRouter is an alternative. It uses URLs such as /#/projects, keeping the route fragment out of server requests, at the cost of less clean URLs.
Common pitfalls to avoid
- Mixing library generations: keep imports, routing syntax, and styling examples consistent with the installed major versions.
- Nesting routers unnecessarily: put shared navigation and routes under one router so they use the same history context.
- Confusing fallback types: the React not-found screen handles unknown client routes; the server fallback ensures React can load at all. It does not automatically produce an HTTP 404 status.
- Treating routes as security: hiding a link or redirecting a page is not authorization. Protected data still needs server-side access checks.
Takeaway
Reliable React navigation needs more than a styled menu. Define clear routes, connect Material-UI controls through router links, show the active section, and configure hosting for direct URL requests. That combination gives your application a consistent interface without sacrificing the browser behavior users already understand.
Conversation (0)
No comments yet. Start the conversation.
Join the conversation
Create a free member account to comment.