DEV Community

Cover image for The right way to structure your react router
Kachi Cheong
Kachi Cheong

Posted on • Edited on

The right way to structure your react router

React Router Tutorial

People new to react generally don't know how to structure their routes.

Beginners and entry level developers will write something like this:

import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";
import Checkout from "./pages/Checkout";
import Login from "./pages/Login";
import Maps from "./pages/Maps";
import Settings from "./pages/Settings";
import Store from "./pages/Store";

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/profile" element={<Profile />} />
        <Route path="/checkout" element={<Checkout />} />
        <Route path="/login" element={<Login />} />
        <Route path="/maps" element={<Maps />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/store" element={<Store />} />
      </Routes>
    </BrowserRouter>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Although this is acceptable for small projects, when your project scales - this will become incredibly difficult to read.

So we're going refactor the code into this:

import "./App.css";
import { BrowserRouter } from "react-router-dom";
import Router from "./pages/router";

const App = () => {
  return (
    <BrowserRouter>
      <Router />
    </BrowserRouter>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

It's cleaner, scalable and more readble. So let's get started!

Firstly create our React app in typescript by running the following commands in our terminal:

npx create-react-app router-tutorial --template typescript
cd router-tutorial
Enter fullscreen mode Exit fullscreen mode

Create the Pages

We're only going to create two pages, Home and About.

Run the following commands in your terminal:

mkdir src/pages
mkdir src/pages/Home src/pages/About
touch src/pages/Home/index.tsx src/pages/About/index.tsx
Enter fullscreen mode Exit fullscreen mode

What did we just do?

  1. Created pages directory.
  2. Created two directories inside of pages: Home and About.
  3. Created index.tsx files for Home and About.

Add this to your pages/About/index.tsx file:

const About = () => {
  return (
    <div>
      <h1>About</h1>
    </div>
  );
};

export default About;
Enter fullscreen mode Exit fullscreen mode

Add this to your pages/Home/index.tsx file:

const Home = () => {
  return (
    <div>
      <h1>Home</h1>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

This is pretty self explanatory, we've created two files which represent our pages.

Creating the types

Let create our types by running the following commands in our terminal:

mkdir src/types
touch src/types/router.types.ts
Enter fullscreen mode Exit fullscreen mode

Now add this to the newly created types/router.types.ts file:

export interface routerType {
  title: string;
  path: string;
  element: JSX.Element;
}
Enter fullscreen mode Exit fullscreen mode

What is happening?

Declare a type for each route:

  • title: this will be a string
  • path: this will also be a string
  • element: this will be a JSX.Element

Why declare types?

You'll see shortly that declaring the types will make sure each time we add a page object, it will follow a strict rule pattern and won't compile any errors.

Creating the Router

Now we're creating our router.

Run this command in your terminal:

touch src/pages/router.tsx src/pages/pagesData.tsx
Enter fullscreen mode Exit fullscreen mode

Pages Data

Add to pages/pagesData.tsx:

import { routerType } from "../types/router.types";
import About from "./About";
import Home from "./Home";

const pagesData: routerType[] = [
  {
    path: "",
    element: <Home />,
    title: "home"
  },
  {
    path: "about",
    element: <About />,
    title: "about"
  }
];

export default pagesData;
Enter fullscreen mode Exit fullscreen mode

What is happening?

  1. We've imported our pages and types.
  2. Added a title, path and element to each object.

Every time we want to add a new page, all we have to do is add a new page object into this array. The types will be strict so they must each contain a title, path and element.

Router File

Add to pages/router.tsx

import { Route, Routes } from "react-router-dom";
import { routerType } from "../types/router.types";
import pagesData from "./pagesData";

const Router = () => {
  const pageRoutes = pagesData.map(({ path, title, element }: routerType) => {
    return <Route key={title} path={`/${path}`} element={element} />;
  });

  return <Routes>{pageRoutes}</Routes>;
};

export default Router;
Enter fullscreen mode Exit fullscreen mode

What is happening?

We're mapping over the pagesData.tsx file and for each object in our data, we are returning a route.

Update App File

Finally update the App.tsx:

import "./App.css";
import { BrowserRouter } from "react-router-dom";
import Router from "./pages/router";

const App = () => {
  return (
    <BrowserRouter>
      <Router />
    </BrowserRouter>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

And we're all done! Thanks for reading.

Here is the Github repo.

Top comments (12)

Collapse
 
cwshields profile image
Chase Shields

What's the cleanest way to pass props into the route element in the pagesData.tsx file? This was the quick fix that VS Code applied, but it seems messy:

{
    path: "blog",
    element: <Blog id={0} title={""} description={""} tags={""} readTime={""} date={""} user={""} />,
    title: "Blog"
  },
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kachiic profile image
Kachi Cheong

this will depend on the arguments of the *Blog *. If they are required I suggest:

const blogArgs = {
 title:"",
 description: "",
 tags="",
 readTime: "",
 date: ""
 user: ""
}

{
    path: "blog",
    element: <Blog {...blogArgs}/>,
    title: "Blog"
  },
Enter fullscreen mode Exit fullscreen mode
Collapse
 
cwshields profile image
Chase Shields

This is much better, thanks! What would you suggest for redirects with this method? Say, for example, if I want to redirect the path from the default "/", to "/home".

Thread Thread
 
lnguyen24794 profile image
Nguyễn Thành Lâm • Edited

You can do like this for less than v5 version
<Switch>
<Redirect exact from="/" to="/home"/>
</Switch>

Or for newest v6
<Route path="/" element={<Navigate to="/home" replace />} />

Collapse
 
joshlavely profile image
Josh Lavely

I really liked your article. Its a topic that needs to be talked about more.

Anyways my only thoughts were to make this lazy.

Then code-splitting can happen and we see big reductions in bundle sizes.

Collapse
 
songhee24 profile image
_/\ • Edited

you can do it like this

const pagesData: routerType[] = [
{
path: "",
element: React.lazy(() => import('./Home')),
title: "home"
},
{
path: "about",
element: React.lazy(() => import('./About')),
title: "about"
}
];

Collapse
 
benjaminsson profile image
Johan Benjaminsson

Interesting way to structure it!
One thing i didn't understand. You changed format from JSX to an array of objects when you moved everything out off app.tsx. Combining that with splitting the code up in multiple files; doesn't that make it harder to read?

Collapse
 
kachiic profile image
Kachi Cheong

Hey Johan,

so actually this is the better practice as when you begin to scale the code it makes it more readable, for example if you have 100 routes for your site you can then begin to separate them like so

*Basic pages *

const basicRoutes = [
  {
    path: "",
    element: <Home />,
    title: "home"
  },
  {
    path: "about",
    element: <About />,
    title: "about"
  }
]
Enter fullscreen mode Exit fullscreen mode

Advance Page

const advancedRoutes = [
  {
    path: "complex",
    element: <Complex />,
    title: "complex"
  },
  {
    path: "assignment",
    element: <Assignment />,
    title: "assignment"
  }
]
Enter fullscreen mode Exit fullscreen mode

All routes

export const allRoutes = [...basicRoutes,...advancedRoutes]
Enter fullscreen mode Exit fullscreen mode

Now actually the update to react router (which is now v6) makes this format moot. Their new structure is basically the same as the one i've suggested here instead of the JSX format. This makes it more readable when scaling.

Collapse
 
alexappelt profile image
alexappelt

Nice way to separate the routes in a single file and organizate it. .

Collapse
 
nilayjain0611 profile image
Nilay Jain

So we have created Types in this to use the routes, but how can we achieve it in javascript?

Collapse
 
paiatpeace profile image
Ameya Pai

But how do you handle nested routes?

Collapse
 
paiatpeace profile image
Ameya Pai • Edited

@kachiic You article helped me. This is how I used your solution for nested routes

router.jsx

import { Route, Routes } from 'react-router-dom';
import pages from '@/pages/pages';

const renderRoutes = (routes) => {
  return routes.map(({ path, title, element, children = [] }) => {
    return (
      <Route key={title} exact path={path} element={element}>
        {children.length > 0 && <Route>{renderRoutes(children)}</Route>}
      </Route>
    );
  });
};

const Router = () => {
  const pageRoutes = renderRoutes(pages);
  return <Routes>{pageRoutes}</Routes>;
};

export default Router;
Enter fullscreen mode Exit fullscreen mode

pages.jsx

const pagesData = [
  {
    path: '',
    element: <Landing />,
    title: 'landing',
  },
{
        path: 'work-experience',
        element: <WorkExperience />,
        title: 'workExperience',
        children: [
          {
            path: 'fresher',
            element: <Fresher />,
            title: 'fresher',
            children: [
              {
                path: 'education',
                element: <Education />,
                title: 'education',
              }]
 }].
}]

export default pagesData;
Enter fullscreen mode Exit fullscreen mode

This way I can achieve multi-level nested routing...