Next.js-like Routing in React with vite-plugin-next-react-router

Bumgu

2024/02/26

Tags: React vite

While taking a course, I ran into an error because vite-plugin-next-react-router no longer worked: its installed version differed from the version at the time the course was recorded. I resolved the error by referring to the official GitHub repository and this blog post.


1. Create a Vite project

Create a Vite project with yarn create vite.

➜ yarn create vite
yarn create v1.22.19
(node:11216) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-vite@5.1.0" with binaries:
      - create-vite
      - cva
✔ Project name: … vite-practice
✔ Select a framework: › React
✔ Select a variant: › TypeScript

Scaffolding project in /Users/location/toyour/project/vite-practice...

Done. Now run:

  cd vite-practice
  yarn
  yarn dev

✨  Done in 13.75s.

Enter a project name and choose the options that suit your needs. I chose React and TypeScript.

Move into the folder with the cd your-project-name command, then install dependencies by entering yarn install or yarn. You can start the local server with yarn dev. Vite runs on port 5173 by default, so visit localhost:5173 to see the page.

2. Install the plugin

Install the plugin with the commands yarn add vite-plugin-next-react-router and yarn add react-router-dom. I also installed react-router-dom because it is required to use vite-plugin-next-react-router.

3. Routing

React routing can be configured dynamically using <BrowserRouter>, <Routes>, and <Route>. Next.js automatically routes based on folder names. The vite-plugin-next-react-router plugin makes Next.js-like routing possible in React.

First, modify App.tsx:

import './App.css'

function App() {
  return (
  <div>
      This is App.tsx.
  </div>
  )
}

export default App

3-1. Create pages for routing

├── README.md
├── index.html
├── package.json
├── public
│   └── vite.svg
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── assets
│   │   └── react.svg
│   ├── index.css
│   ├── main.tsx
│   ├── pages
│   │   ├── index.tsx
│   │   └── products
│   │       ├── [id].tsx
│   │       └── index.tsx
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock
  1. Create a pages folder inside src.
  2. Create index.tsx inside src/pages. This file will provide the component returned at locahost:5173/.
  3. Create a products folder inside src/pages.
  4. Create [id].tsx and index.tsx inside src/pages/products. 4-1. [id].tsx provides dynamic routing for a product number. For example: localhost:5173/products/22 4-2. index.tsx is the localhost:5173/products page that displays the product list.

3-2. Prepare routing

When you create a project with Vite, there is a vite.config.ts or vite.config.js in the project root.

vite.config.ts

Add withReactRouter() to the plugins array.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import withReactRouter from 'vite-plugin-next-react-router';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(),
  withReactRouter()],
})

main.tsx

Inside the src folder, main.tsx is like the index.js created when you create a React app with CRA. Wrap <App /> in react-router-dom’s <BrowserRouter>.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './app.tsx'
import { BrowserRouter } from 'react-router-dom';

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

src/pages/index.tsx, products/index.tsx, [id].tsx

We’ll just display a simple screen to confirm that routing works.

const IndexPage = () => {
  return (
  <div>
      This is the src/pages/index.tsx page.
  </div>
  )
} 

export default IndexPage;
const ProductList = () => {
  return (
  <div>
      This is the src/pages/products/ProductList page.
  </div>
  )
}

export default ProductList;
import { useParams } from "react-router-dom";

const ProductDetail = () => {
  let { id } = useParams<"id">();
  return (
  <div>
      Product details for item {id}.
  </div>
  )
}

export default ProductDetail;

3-3. _layout.tsx

I wrote this by referring to the official GitHub repository. Create it under the src/pages path.

import React, { Suspense } from 'react';
import { Outlet } from 'react-router-dom';

const Layout: React.FC = () => {
  return (
  <div>
    <Suspense fallback={'loading...'}>
      <Outlet />
    </Suspense>
  </div>
  )
}

export default Layout;

3-4. routes.tsx

This is where the problem caused by the plugin version upgrade occurred. In the course, routes.tsx was generated automatically, but it wasn’t generated for me, so I wrote it myself. Create it under the src path.

import { lazy } from "react";
import GlobalLayout from "./pages/_layout";

const Index = lazy(() => import("./pages/index"));
const PoroductsIndex = lazy(() => import("./pages/products/index"));
const ProductsId = lazy(() => import("./pages/products/[id]"));

export const routes = [
  {
    path: "/",
    element: <GlobalLayout />,
    children: [
      { path: "/", element: <Index /> },
      { path: "/products", element: <PoroductsIndex /> },
      { path: "/products/:id", element: <ProductsId /> },
    ],
  },
];

export const pages = [
  { route: "/" },
  { route: "/products" },
  { route: "/products/:id" },
];

The / path displays the Index component, and /products displays ProductsIndex (the products/index.tsx file). vite-plugin-next-react-router uses the react-router approach: this routes.tsx file configures which component to display for each path.

3-5. App.tsx

As you know, Next.js has a global file like _app.tsx. App.tsx will now play a similar role to _app.tsx.

import { useRoutes } from 'react-router-dom';
import { routes } from './routes';

const App = () => {
  const elem = useRoutes(routes)
  return elem
}

export default App;

We use the userRoutes hook with the routes.tsx file we wrote, assign it to a variable named elem, and return it.

Routing should now work properly. Let’s check.

4. Result


You can also see that the displayed value changes according to the id in products/[id]. I’ll end the post here. Thank you.

>> Home