title: SSR Routing with React and React Router
body: <p>I have an existing application that was created with <code>create-react-app</code> and I would like to add Server Side Routing to this, primarily for the SEO benefits.</p>
  <p>Following several tutorials, I have a basic working application, however I can't seem to get it to play nicely with react-router. I have one of two scenarios: either the routing works, but the functionality doesn't (ie. no event handlers, CSS is wonky, etc.) or the functionality is there, but the routing doesn't work (ie. only loads the default route <code>/</code>, all other routes result in a <code>Cannot GET /route</code>.</p>
  <p>I have included the following files in the question:</p>
  <p><code>server/index.js</code> - This is the server that renders the app and serves it</p>
  <p><code>webpack.server.js</code> - Builds</p>
  <p><code>.babelrc.json</code> - Babel Configuration</p>
  <p><code>src/index.tsx</code> - The main client side entry point</p>
  <p><code>src/App.tsx</code> - The application itself</p>
  <p><code>src/routes.tsx</code> - The routes that I wish to use</p>
  <p>Inside of <code>server/index.js</code> if I route to <code>'*'</code>, then the site will route everything correctly. I will be able to route to <code>/render/</code> or <code>/display</code> and I will see the appropriate pages, I won't however be able to actually click anything, because it seems as though it hasn't been properly hydrated. None of the data fetching works, and none of the click handlers do either. If I instead use <code>'/'</code>, then the site will actually function, however I won't be able to route to other pages. Not even the 404 works.</p>
  <p>Ultimately I would like to know how to best configure this to properly work. I have considered also switching to a framework like Next.js however this would be a significant undertaking, so I'm not sure that it would be worth it.</p>
  <p>The jist of the application looks like this:</p>
  <p><code>server/index.js</code></p>
  <pre class="lang-js prettyprint-override"><code>import React from 'react'
  import ReactDOMServer from 'react-dom/server'
  import express from 'express'
  import { StaticRouter } from 'react-router-dom/server'
  import path from 'path'
  import fs from 'fs'
  import App from '../src/App'
  
  const PORT = process.env.PORT || 3006
  const app = express()
  
  app.get('/', (req, res) =&gt; { // &lt;- Changing this between '*' and '/'
    const context = {}
    const app = ReactDOMServer.renderToString(
      &lt;StaticRouter location={req.url} context={context}&gt;
        &lt;App /&gt;
      &lt;/StaticRouter&gt;
    )
    const indexFile = path.resolve('./build/index.html')
  
    fs.readFile(indexFile, 'utf8', (err, data) =&gt; {
      if (err) {
        console.error('Something went wrong:', err)
        return res.status(500).send('Oops, better luck next time!')
      }
  
      return res.send(
        data.replace('&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;', `&lt;div id=&quot;root&quot;&gt;${app}&lt;/div&gt;`)
      )
    })
  })
  
  app.use(express.static('./build'))
  
  app.use(
    '/',
    (req, res, next) =&gt; {
      console.log('Request URL:', req.originalUrl)
      console.log('URL:', req.url)
      next()
    },
    (req, res, next) =&gt; {
      console.log('Request Type:', req.method)
      next()
    }
  )
  
  app.listen(PORT, () =&gt; {
    console.log(`Server is listening on port ${PORT}`)
  })
  </code></pre>
  <p><code>webpack.server.js</code></p>
  <pre class="lang-js prettyprint-override"><code>const path = require('path')
  const nodeExternals = require('webpack-node-externals')
  
  module.exports = {
    entry: './server/index.js',
    target: 'node',
    externals: [nodeExternals()],
    output: {
      path: path.resolve('server-build'),
      filename: 'index.js',
    },
    module: {
  
      rules: [
        {
          test: /\.ts(x?)$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'babel-loader',
            },
            {
              loader: 'ts-loader',
            },
          ],
        },
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'babel-loader',
            },
          ],
        },
      ],
    },
    resolve: {
      extensions: ['.js', '.ts', '.tsx'],
    },
  }
  </code></pre>
  <p><code>.babelrc.json</code></p>
  <pre class="lang-json prettyprint-override"><code>{
      &quot;presets&quot;: [
          &quot;@babel/preset-env&quot;,
          &quot;@babel/preset-react&quot;
      ]
  }
  </code></pre>
  <p><code>src/index.tsx</code></p>
  <pre class="lang-js prettyprint-override"><code>import React from 'react'
  import ReactDOM from 'react-dom'
  import './index.css'
  import App from './App'
  import { BrowserRouter } from 'react-router-dom'
  
  ReactDOM.hydrate(
    &lt;React.StrictMode&gt;
      &lt;BrowserRouter&gt;
        &lt;App /&gt;
      &lt;/BrowserRouter&gt;
    &lt;/React.StrictMode&gt;,
    document.getElementById('root') as HTMLElement
  )
  </code></pre>
  <p><code>src/App.tsx</code></p>
  <pre class="lang-js prettyprint-override"><code>import React from 'react'
  import { createTheme, ThemeProvider } from '@mui/material/styles'
  import defaultTheme from './default'
  import Router from './routes'
  import { QueryClient, QueryClientProvider } from 'react-query'
  import {
    ExtendedStringifyOptions,
    QueryParamProvider,
    transformSearchStringJsonSafe,
  } from 'use-query-params'
  import { useNavigate, useLocation, Location } from 'react-router-dom'
  
  const App = () =&gt; {
    const theme = createTheme(defaultTheme)
  
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          refetchOnWindowFocus: false,
          retry: false,
          staleTime: 30000,
        },
      },
    })
  
    const queryStringifyOptions: ExtendedStringifyOptions = {
      transformSearchString: transformSearchStringJsonSafe,
    }
  
    // Needed this in order to use useQueryParams with React Router v6
    // From https://www.robinwieruch.de/react-router-search-params/
    const RouteAdapter = ({ children }: any) =&gt; {
      const navigate = useNavigate()
      const location = useLocation()
  
      const adaptedHistory = React.useMemo(
        () =&gt; ({
          replace(location: Location) {
            navigate(location, { replace: true, state: location.state })
          },
          push(location: Location) {
            navigate(location, { replace: false, state: location.state })
          },
        }),
        [navigate]
      )
      return children({ history: adaptedHistory, location })
    }
  
    return (
      &lt;QueryParamProvider
        stringifyOptions={queryStringifyOptions}
        ReactRouterRoute={RouteAdapter}
      &gt;
        &lt;QueryClientProvider client={queryClient}&gt;
          &lt;ThemeProvider theme={theme}&gt;
            &lt;Router /&gt;
          &lt;/ThemeProvider&gt;
        &lt;/QueryClientProvider&gt;
      &lt;/QueryParamProvider&gt;
    )
  }
  
  export default App
  </code></pre>
  <p><code>src/routes.tsx</code></p>
  <pre><code>import React from 'react'
  import { Routes, Route } from 'react-router-dom'
  import DisplayView from './views/display-view'
  import EmptyView from './views/empty-view'
  import RenderView from './views/render-view'
  
  const Router = (): JSX.Element =&gt; {
    return (
      &lt;Routes&gt;
        &lt;Route path={'/'} element={&lt;RenderView /&gt;} /&gt;
        &lt;Route path={`/render`} element={&lt;RenderView /&gt;} /&gt;
        &lt;Route path={`/display`} element={&lt;DisplayView /&gt;} /&gt;
        &lt;Route path='*' element={&lt;EmptyView /&gt;} /&gt;
      &lt;/Routes&gt;
    )
  }
  
  export default Router
  </code></pre>
  <p><code>package.json</code></p>
  <pre><code>{
     ...
     scripts: {
     &quot;dev:build-server&quot;: &quot;cross-env NODE_ENV=development webpack --config webpack.server.js --mode=development -w&quot;,
      &quot;dev:start&quot;: &quot;nodemon ./server-build/index.js&quot;,
      &quot;dev&quot;: &quot;npm-run-all --parallel build dev:*&quot;   
     }
     ...
  }
  </code></pre>
  <p><strong>Update</strong>: The order inside the <code>server/index.js</code> file seems to make a big difference. If I switch the order of the line: <code>app.use(express.static('./build'))</code> to come at the very beginning, then things seem to work themselves out, and the app will route properly, as well as function as it should.</p>