{"id":16500194,"url":"https://github.com/toomuchdesign/next-react-router","last_synced_at":"2025-08-01T23:32:17.846Z","repository":{"id":66163107,"uuid":"199040728","full_name":"toomuchdesign/next-react-router","owner":"toomuchdesign","description":"A documented attempt of using Next.js + react-router","archived":false,"fork":false,"pushed_at":"2020-11-10T12:04:12.000Z","size":275,"stargazers_count":51,"open_issues_count":4,"forks_count":15,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-12-02T16:11:29.310Z","etag":null,"topics":["nextjs","react-router","reactjs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/toomuchdesign.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-26T15:29:40.000Z","updated_at":"2024-04-22T05:04:33.000Z","dependencies_parsed_at":null,"dependency_job_id":"d2e83f2f-5625-4ac1-bbb0-7062a20f7440","html_url":"https://github.com/toomuchdesign/next-react-router","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toomuchdesign%2Fnext-react-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toomuchdesign%2Fnext-react-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toomuchdesign%2Fnext-react-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toomuchdesign%2Fnext-react-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toomuchdesign","download_url":"https://codeload.github.com/toomuchdesign/next-react-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228417193,"owners_count":17916483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nextjs","react-router","reactjs"],"created_at":"2024-10-11T14:56:14.650Z","updated_at":"2024-12-06T05:26:07.702Z","avatar_url":"https://github.com/toomuchdesign.png","language":"JavaScript","readme":"# Next.js + react-router\n\nThis repo documents an attempt of using [Next.js](https://github.com/zeit/next.js/) (preserving native SSR features) with the following setup:\n\n- Single entry point (like [Create React App](https://github.com/facebook/create-react-app) and [Hops](https://github.com/xing/hops)). No file system-based routing\n- [react-router](https://github.com/ReactTraining/react-router) as only routing system\n\nThis document is available as:\n- [GitHub repository](https://github.com/toomuchdesign/next-react-router)\n- [dev.to post](https://dev.to/toomuchdesign/next-js-react-router-2kl8)\n\n## Disclaimers\n\n- Next.js team strongly advises against this approach.\n- This experiment was carried out at the times of Next.js v9.3: the framework has changed a lot since then.\n\n## Part one, basic setup\n\n### 1 - Install Next.js\n\nRelevant [repo commit][1-initial-setup].\n\n[Install NextJS](https://nextjs.org/docs#setup) as usual and create the **single entry point** file at `pages/index.js`.\n\n### 2 - Redirect all requests to single entrypoint\n\nRelevant [repo commit][2-redirect-to-entrypoint].\n\nIn order to skip file system-based routing, we'll configure a [custom Next.js server](https://nextjs.org/docs#custom-server-and-routing) to forward all the requests to our single entrypoint.\n\nWe'll use Next.js [`Server.render` method](https://github.com/zeit/next.js/blob/2b1a5c3eb4f67a30e1a9000d7d21e14bbe536687/packages/next-server/server/next-server.ts#L405) to render and serve the entrypoint.\n\n```js\n// server.js\nconst express = require('express');\nconst nextJS = require('next');\n\nasync function start() {\n  const dev = process.env.NODE_ENV !== 'production';\n  const app = nextJS({dev});\n  const server = express();\n  await app.prepare();\n\n  // Redirect all requests to main entrypoint pages/index.js\n  server.get('/*', async (req, res, next) =\u003e {\n    try {\n      app.render(req, res, '/');\n    } catch (e) {\n      next(e);\n    }\n  });\n\n  server.listen(3000, err =\u003e {\n    if (err) throw err;\n    console.log(`\u003e Ready on http://localhost:3000`);\n  });\n}\n\nstart();\n```\n\nRun the dev server, and the entrypoint page at `pages/index.js` should be served as response for any requested url. 👊\n\n### 3 - Introduce react-router\n\nRelevant [repo commit][3-introduce-react-router].\n\nIn order to get different responses according to the requested url we need a routing system.\n\nWe'll use `react-router` (see it's [docs about SSR](https://reacttraining.com/react-router/web/guides/server-rendering)) and wrap the application with a `StaticRouter` or a `BrowserRouter` based on the environment application environment (server or browser).\n\nInstall `react-router` and `react-router-dom`:\n\n```\nnpm i react-router react-router-dom -S\n```\n\n...and update the `pages/index.js` entrypoint to use some `Link` and `Route` components from `react-router-dom` (see repo).\n\nLet's now declare a `withReactRouter` HOC to wrap the application with the proper router:\n\n```js\n// next/with-react-router.js\nimport React from 'react';\nimport {BrowserRouter} from 'react-router-dom';\nconst isServer = typeof window === 'undefined';\n\nexport default App =\u003e {\n  return class AppWithReactRouter extends React.Component {\n    render() {\n      if (isServer) {\n        const {StaticRouter} = require('react-router');\n        return (\n          \u003cStaticRouter\n            location={this.props.router.asPath}\n          \u003e\n            \u003cApp {...this.props} /\u003e\n          \u003c/StaticRouter\u003e\n        );\n      }\n      return (\n        \u003cBrowserRouter\u003e\n          \u003cApp {...this.props} /\u003e\n        \u003c/BrowserRouter\u003e\n      );\n    }\n  };\n};\n```\n\n...and wrap the application with `withReactRouter` HOC:\n\n```js\n// pages/_app.js\nimport App, {Container} from 'next/app';\nimport React from 'react';\nimport withReactRouter from '../next/with-react-router';\n\nclass MyApp extends App {\n  render() {\n    const {Component, pageProps} = this.props;\n    return (\n      \u003cContainer\u003e\n        \u003cComponent {...pageProps} /\u003e\n      \u003c/Container\u003e\n    );\n  }\n}\n\nexport default withReactRouter(MyApp);\n```\n\nRun the dev server, and you should be able to see your routes live and server side rendered.\n\n## Part two, context information\n\nOne of my favourite `react-router` features consists of the possibility of [adding context information](https://reacttraining.com/react-router/web/guides/server-rendering/adding-app-specific-context-information) during the rendering phase and **returning server side responses** based on the information collected into the **`context` object**.\n\nThis enables client side code to take control of the responses returned by the node server like **returning a HTTP 404** instead of a \"not found page\" or returning a **real HTTP 302 redirect** instead of a client side one.\n\nIn order to achieve this behaviour we have to configure Next.js to do the following:\n\n1. render the requested page providing a context object to the app router\n2. check whether context object was mutated during the rendering process\n3. decide whether to return the rendered page or do something else based on context object\n\n### 4 - Provide context object to the router\n\nRelevant [repo commit][4-provide-context].\n\nWe'll inject an empty `context` object into Express' `req.local` object and make it available to the router application via [React Context](https://reactjs.org/docs/context.html).\n\nLet's inject `context` object into Express' `req.local` object:\n\n```diff\n// server.js\nserver.get('/*', async (req, res, next) =\u003e {\n  try {\n+   req.locals = {};\n+   req.locals.context = {};\n    app.render(req, res, '/');\n```\n\nNext.js provides a `req` and `res` objects as props of [`getInitialProps` static method](https://nextjs.org/docs#fetching-data-and-component-lifecycle). We'll fetch `req.originalUrl` and `req.locals.context` and handle it over to the static router.\n\n```diff\n// next/with-react-router.js\n  return class AppWithReactRouter extends React.Component {\n+   static async getInitialProps(appContext) {\n+     const {\n+       ctx: {\n+         req: {\n+           originalUrl,\n+           locals = {},\n+         },\n+       },\n+     } = appContext;\n+     return {\n+       originalUrl,\n+       context: locals.context || {},\n+     };\n+   }\n\n  // Code omitted\n          \u003cStaticRouter\n-           location={this.props.router.asPath}\n+           location={this.props.originalUrl}\n+           context={this.props.context}\n          \u003e\n```\n\n### 5 - Separate rendering and response\n\nRelevant [repo commit][final-setup].\n\nSince we want to provide extra server behaviours based on `req.locals.context` in-between SSR and server response, Next.js `Server.render` falls short of flexibility.\n\nWe'll re-implement `Server.render` in `server.js` using Next.js `Server.renderToHTML` and `Server.sendHTML` methods.\n\nPlease note that some code was omitted. Refer to the source code for the complete implementation.\n\n```diff\n// server.js\n  server.get('/*', async (req, res, next) =\u003e {\n    try {\n+     // Code omitted\n\n      req.locals = {};\n      req.locals.context = {};\n-     app.render(req, res, '/');\n+     const html = await app.renderToHTML(req, res, '/', {});\n+\n+     // Handle client redirects\n+     const context = req.locals.context;\n+     if (context.url) {\n+       return res.redirect(context.url)\n+     }\n+\n+     // Handle client response statuses\n+     if (context.status) {\n+       return res.status(context.status).send();\n+     }\n+\n+     // Code omitted\n+     app.sendHTML(req, res, html);\n    } catch (e) {\n```\n\nBefore sending the response with the rendered HTML to the client, we now check the `context` object and redirect or return a custom HTTP code, if necessary.\n\nIn order to try it out, update the `pages/index.js` entrypoint to [make use of `\u003cRedirect\u003e` and `\u003cStatus\u003e` components](https://github.com/toomuchdesign/next-react-router/blob/master/pages/index.js) and start the dev server.\n\n## Summary\n\nWe showed how it's be possible to setup Next.js take full **advantage of `react-router`**, enabling **single entrypoint** approach and fully **preserving SSR**.\n\nIn order to do so we:\n1. Redirected all server requests to a **single entrypoint**\n2. **Wrapped** the application (using HOC) with the proper **`react-router` router**\n3. Injected `req` server object with a **`locals.context` object**\n4. Provided **HOC wrapper** with `req.locals.context` and `req.originalUrl`\n5. **Extended next.js `Server.render`** to take into account `req.locals.context` before sending HTML\n\nThe re-implementation of `Server.render` in userland code is the most disturbing part of it, but it might be made unnecessary by extending a bit `Server.render` API in Next.js.\n\n### Results\n\n#### `react-router` rendered server side\n\nreact-router's `\u003cRoute\u003e` components get **statically rendered** on the server based on received [`req.originalUrl`](https://expressjs.com/en/api.html#req.originalUrl) url.\n\n![Server side render](/docs/ssr.png)\n\n#### HTTP 302 redirect triggered by client code\n\nWhen server rendering process encounters `\u003cRedirect from=\"/people/\" to=\"/users/\" /\u003e` component, the server response will return an **HTTP 302 response** with the expected **`Location` header**.\n\n![HTTP 302 redirect](/docs/302.png)\n\n#### HTTP 404 triggered by client code\n\nWhen server rendering process encounters `\u003cStatus code={404}/\u003e` component, the **server** response returns an **HTTP response** with the **expected status code**.\n\n![HTTP 404 redirect](/docs/404.png)\n\n### Further consideration\n\nI'm sure this setup is way far from being optimal. I'll be happy take into account any suggestions, feedbacks, improvements, ideas.\n\n### Issues\n\n- Static pages not being exported\n- Dev mode cannot build requested route on demand\n- `getInitialProps` not implemented\n\n[1-initial-setup]: https://github.com/toomuchdesign/next-react-router/tree/1-initial-setup\n[2-redirect-to-entrypoint]: https://github.com/toomuchdesign/next-react-router/tree/2-redirect-to-entrypoint\n[3-introduce-react-router]: https://github.com/toomuchdesign/next-react-router/tree/3-introduce-react-router\n[4-provide-context]: https://github.com/toomuchdesign/next-react-router/tree/4-provide-context\n[final-setup]: https://github.com/toomuchdesign/next-react-router/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoomuchdesign%2Fnext-react-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoomuchdesign%2Fnext-react-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoomuchdesign%2Fnext-react-router/lists"}