27

Goal: I would like to achieve github style routing, where abcd in github.com/abcd could resolve to a user profile page or a team page.

I currently have a version that sort of works (see below). Unfortunately I am occasionally getting a white page flash when navigating between 2 dynamic routes.

My server file looks like:

const express = require('express');
const next = require('next');
const { parse } = require('url');
const resolveRoute = require('./resolveRoute');

const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({
  dev,
});
const nextHandle = nextApp.getRequestHandler();

const STATIC_ROUTES = [
  '/about',
  '/news',
  '/static',
];

const DYNAMIC_ROUTE_MAP = {
  user: '/[user]',
  team: '/teams/[team]',
};

nextApp.prepare().then(() => {
  const server = express();

  server.get('*', async (req, res) => {
    // pass through next routes
    if (req.url.indexOf('/_next') === 0) {
      return nextHandle(req, res);
    }

    // pass through static routes
    if (
      req.url === '/' ||
      STATIC_ROUTES.map(route => req.url.indexOf(route) === 0).reduce(
        (prev, curr) => prev || curr,
      )
    ) {
      return nextHandle(req, res);
    }

    // try to resolve the route
    // if successful resolves to an object:
    // { type: 'user' | 'team' }
    const resolvedRoute = await resolveRoute(req.url);
    if (!resolvedRoute || !resolvedRoute.type) {
      console.error(' Unable to resolve route...');
      return nextHandle(req, res);
    }

    // set query
    const { pathname } = parse(req.url);
    const paths = pathname.split('/').filter(path => path.length > 0);
    const query = {
      [resolvedRoute.type]: paths.length > 0 ? paths[0] : null,
    };

    // render route
    return nextApp.render(
      req,
      res,
      DYNAMIC_ROUTE_MAP[resolvedRoute.type],
      query,
    );
  });

  server.listen(port, err => {
    if (err) throw err;
    console.log(` Ready on http://localhost:${port}`);
  });
});

I'm wondering if there is a better way to handle this or if I need to move away from NextJS.

csbarnes
  • 1,873
  • 2
  • 28
  • 35

3 Answers3

49

Next.JS has built in dynamic routing, which shouldn't require you to create a custom server.js file. If you want full compatibility with Next.JS you should use it's dynamic routing instead.

To create a dynamic route in Next.JS you can create pages with names surrounded in square brackets e.g. /pages/[username].js. This will match all routes on your base domain, so you can set up the example you mentioned with github e.g. http://yourwebsite.com/csbarnes and http://yourwebsite.com/anotherusername.

In the example above you can grab the username in your Next.JS page from the query parameter in getInitialProps just in the same way as you would with any query string parameters:

static getInitialProps({query}) {
  console.log(query.username); // the param name is the part in [] in your filename
  return {query}; // you can now access this as this.props.query in your page
}

Next.JS always matches static routes before dynamic routes meaning your /pages/ directory can look like this:

pages/index.js       -> (will match http://yourwebsite.com)
pages/about.js       -> (will match http://yourwebsite.com/about)
pages/contact.js     -> (will match http://yourwebsite.com/contact)
pages/[username].js  -> (will match http://yourwebsite.com/[anything_else])

Multiple segments

You can have multiple segment dynamic routes, such as http://website.com/[username]/[repo] using folders in your pages directory:

pages/[username].js      -> (matches http://yourwebsite.com/[username])
pages/[username]/[repo]  -> (matches http://yourwebsite.com/[username]/[repo])

In this instance your query object will contain 2 params: { username: ..., repo: ...}.

Route "prefixes"

You can have multiple dynamic routes with different "prefixes" if you wish by creating folders in your pages directory. Here is an example folder structure with a website.com/[username] route and a website.com/teams/[team] route:

multiple dynamic routes

Dynamic number of different segments

You can also have dynamic routes with any number of dynamic segments. To do this you need to use an ellipsis ("...") in your dynamic route file name:

/pages/[...userDetails].js  -> (will match http://website.com/[username]/[repo]/[etc]/[etc]/[forever]) 

In this instance your this.props.userDetails variable will return an array rather than a string.

Thomas Clayson
  • 29,657
  • 26
  • 147
  • 224
  • 4
    Right, the issue is I want two dynamic routes, `/pages/[username].js` and `/pages/[team].js`, which doesn't work (understandably). – csbarnes Jan 17 '20 at 17:00
  • 1
    In your question your "teams" route is `http://website.com/teams/[teamname]` so you can do that in NextJS no problem. Just put a folder in your pages directory called `teams` so you have `/pages/[username].js` and `pages/teams/[team].js`. This should work :) – Thomas Clayson Jan 17 '20 at 17:09
  • I have just tested that it works. Here is my folder structure: https://i.imgur.com/H9PfW29.jpg – Thomas Clayson Jan 17 '20 at 17:16
  • I also edited my main post with an update showing how you can implement dynamic routes that encompass a dynamic number of segments, if that's of interest. :) – Thomas Clayson Jan 17 '20 at 17:24
  • Thanks for taking the time to respond but that doesn't help with the original question. I know I can nest dynamic routes under a separate folder but I'm wanting github style routes where `abcd` in `github.com/abcd` could resolve to a user profile page or a team page. – csbarnes Jan 17 '20 at 17:29
  • 3
    Ah then you need to handle that in your page code. I.e. Grab the string, figure out if it's a team or a user and then import the right component to display the right page. – Thomas Clayson Jan 18 '20 at 18:06
  • @ThomasClayson I tried using dynamic route at root, like example.com/[id], however it creates a double render when the browser tries to fetch favicon at root as well. Have you experienced the same, and if so how to solve it? – Frizzo Sep 07 '20 at 14:10
  • 3
    The real answer: that's a badly-designed route scheme, and having a dynamic route as the root is going to trigger a variety of headaches later as the site expands. Make properly prefixed routes for dynamic routing - in this case, `/pages/users/[username].js` and `/pages/teams/[team].js` would make things much simpler. I highly doubt there's a requirement that the user route be at the root level like that. – bsplosion Apr 05 '21 at 20:49
  • Wait till you add ssr caching strategy, using next default routing [bla-bla].js is worthless. – dryleaf Apr 14 '21 at 09:43
2

One addition regarding usage of SSR and SSG pages and you need to differentiate those with dynamic URLs by adding the '-ssr' prefix to an URL.

For example, you need some pages to be SSR, then you can create under the pages an ssr folder where you could put the page [[...path]].js with getServerSideProps. Then you could use such rewrite in the next.config.js under async rewrites() {:

{
    source: '/:path*/:key-ssr', destination: '/ssr/:path*/:key-ssr'
}

that covers such URLs:

  • /page-ssr
  • /en/page1/page-ssr
  • /en/page1/page2/page-ssr
  • /en/page1/page2/page3/page-ssr

etc.

aleks korovin
  • 724
  • 4
  • 6
0

You can't have two different types of dynamic routes at one route. The browser has no way of differentiating between a and b, or in your case a username and a team name.

What you can do is have subroutes, lets say /users and /teams, that have their own respective dynamic routes.

The folder structure for that in NextJS would look like this:

/pages/users/[name].js
/pages/teams/[name].js
Wizard
  • 462
  • 1
  • 6
  • 14