5

We're using Next.Js in next export mode (static HTML export), and we need advanced dynamic routing.

Our routes will look like /[config1]/[config2]/[optionalConfig?]/page, where one segment is optional and the page names are fixed. For example a/b/c/page1 or a1/b1/page2. The pages need the configuration segment data to render.

I haven't found any way to do this with the built-in routing. I can do /pages/[config1]/[config2]/page1.tsx, but that optional segment seems to be an issue. Note that a custom server does not appear to be an option, as we have to use next export mode due to other constraints.

NOTE: We don't know the paths at build time; they represent part of our runtime configuration. This has to use client-side routing. (We do know the finite set of pages - say page1 ... page10 - but the routes to those pages will vary.)

I've tried switching to React Router, setting useFileSystemPublicRoutes: false and adding routes to pages/_app.tsx (Custom App). That almost works, but I see many 404s for on-demand-entries-utils.js in the console as well as some "Possible EventEmitter memory leak detected" warnings (in development mode).

Valid solutions (must work 100% client-side):

  • Way to do this with built-in routing
  • Example of integrating React Router with Next.Js
  • Alternative library (I've looked at next-routes but that hasn't been updated in 3 years)

UPDATE

We may be able to eliminate the requirement of an optional segment. However, it appears that we have to implement getStaticPaths and specify all of the routes. For example:

pages/[config]/foo.tsx

export async function getStaticPaths() {
  // Runs at build time
  return {
    paths: [{ params: { config: 'xyz' } }],
    fallback: false,
  };
}

export async function getStaticProps(context) {
  return {
    props: {},
  };
}

export default function FooPage(): JSX.Element {
  return <div>FOO</div>;
}

This will generate

┌ ○ /
├   /_app
├ ● /[config]/foo
├   └ /xyz/foo

The issue is that we do not know the config at build time.

We need dynamic client-side routing. We'd like to stay with Next.js, as eventually we may be able to use SSR, but that's not an option at the moment.

TrueWill
  • 25,132
  • 10
  • 101
  • 150
  • 1
    I'd suggest looking into the built-in [optional catch all routes](https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes). – juliomalves May 11 '21 at 22:17
  • 1
    @juliomalves Catch-all only works with page names, not directory names. I could probably make a catch-all page, then conditionally render component "pages" based on the query object, but that seems like a hack. – TrueWill May 11 '21 at 22:43
  • 1
    It would generate pages for whatever paths you'd define in `getStaticPaths`. You just have to make sure that `getStaticProps` then handles the params (passed by `getStaticPaths`) correctly when generating those pages. – juliomalves May 11 '21 at 22:50
  • @juliomalves We don't know the paths at build time. – TrueWill May 11 '21 at 22:57
  • How are you using static HTML export with dynamic routes then? That's done at build time. – juliomalves May 11 '21 at 22:58
  • @juliomalves It appears that we can't use Next.Js dynamic routes. The app is still in development. – TrueWill May 11 '21 at 23:02
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/232255/discussion-between-truewill-and-juliomalves). – TrueWill May 11 '21 at 23:08
  • 1
    I built my whole app assuming that dynamic routes would work on export based on them working in dev glad I’m not the only one running up against this. This is super unintuitive, Based on the number of issues I’m seeing around this there should be a huge disclaimer on next.js docs “DYNAMIC ROUTES DO NOT ROUTE DYNAMICALLY ON EXPORT” or something – dm03514 Feb 03 '23 at 12:41

1 Answers1

3

You can create a catch-all route to grab the parameters, including the optional one, and then you'll need to render the right component based on that. So if you created:

pages/[...config].tsx

This is the same as:

pages/[...config]/index.tsx

Then in index.tsx, you can get the config as an array of strings in getStaticProps based on the url. So /config1/config2/optional is [config1, config2, optional] and /config1/config2 is [config1, config2].

So you can use this as a directory path of sorts if you need to add additional subpages under the known and optional paths.

I'm Joe Too
  • 5,468
  • 1
  • 17
  • 29
  • 3
    Unfortunately I've learned that dynamic routes don't work with static HTML export unless you predefine the paths at build time - and we don't know that. I've updated the question to reflect this. – TrueWill May 11 '21 at 23:05
  • 6
    Then you only have three options - 1) make those pages server side rendered which you said is not an option, 2) spin that portion of your app off into a separate single-page application with something like create react app and react router (not ideal), or 3) use url parameters instead of url paths - myapp.com/?config1=foo&config2=bar, etc and then read those in a `useEffect` in next to ping your server. We do this on an e-comm site for order history. The users don't care - it's just us devs that hate it. – I'm Joe Too May 11 '21 at 23:09
  • 1
    Sounds like 2 is our only option - the others won't be acceptable to our stakeholders. Thanks. – TrueWill May 11 '21 at 23:19
  • That sucks because it's definitely the most labor intensive and probably the one most prone to bugs and integration problems. I'd definitely advocate for #1 or #3 if possible, but good luck however it works out! – I'm Joe Too May 11 '21 at 23:21
  • 2
    I think you nailed it with your three options. We confirmed with someone from Vercel that this isn't going to work with next export. Putting React Router in a custom 404 page works for option 2, but it's a hack. https://github.com/brajevicm/next-universal-route#readme is a maybe. – TrueWill May 13 '21 at 19:27
  • 1
    I searched so long for this option 3!! we were using useEffect fetching data based on params so this requires minimal change when exporting to static (also specify `module.exports = { trailingSlash: true, }` in `next.config.js`) , it's perfect. – guckmalmensch Dec 16 '22 at 11:47