gatsby: "mixed" dynamic route not working

Summary

Let me explain my use case and what I mean by “mixed” dynamic route.

It’s a small ads app, currently available at https://musing-davinci-538143.netlify.app/ - minus this attempt for “mixed” dynamic routes.

The /search route can have different configurations: /search is the overall search page whence drill down searches can be performed /search/{country}/{root-category}/{parent-category}, country level searches /search/{place}/{root-category}/{parent-category/{category} are the drill down routes.

There are about 200 categories and place can be any number. In the case of US, that would be just short of 20,000 cities, towns and villages.

You can see that this can become a huge number if one attempts to create pages for all possible combinations of categories and places.

Initially I chose to create pages only for the places that have an actual ad active, and that works fine as you can see in the link above. Those were created using the createPages API ( see code below).

Now, once an user creates an ad he/she should be able to immediately see its ad published and, of course, that ad should be available for all visitors to see.

If the user creates an ad that belongs to one of the pre-built /search/... pages there would be no issue as I fetch current information on componentDidMount on the /src/templates/search.js.

But for the not previously built pages I need to create routes on the fly - dynamic - to show that newly created ad.

Of course those routes should properly handle any valid search still not having a pre-built page and either show the ads or a proper message to the visitor otherwise. IT SHOULD ABSOLUTELY NOT END UP IN 404

It’s certainly impractical and very expensive to re-build the app each time an ad is posted, so I was attempting to generate dynamic routes for the all routes not generated by createPages.

The, once a day or any other time interval, I would rebuild the app, generating new pages if applicable. ( I do have a question on that, as well, but let’s leave it for later)

But these “mixed” dynamic route are not being generated as it seems. It will just send any URL that does not match one generates by createPages to 404.

Relevant information

gatsby.node.js

const path = require('path');
const { urlWriter } = require('./src/tools/urlWriter');
require('dotenv').config({
    path: `.env.${process.env.NODE_ENV}`,
 });

module.exports = { 
//  ***** THIS SECTION IS WORKING
createPages: async ({ graphql, actions }) => {
    const { createPage } = actions;
    const component = path.resolve('./src/templates/search.js');

    const pages = await graphql(`
        {
            myApi {
                categories {
                    id
                    title
                    parent {
                        id
                        title
                    }
                    root {
                        id
                        title
                    }
                }
                collectionQueryAddresses {
                    city
                }
            }
        }
    `);
    const categories = pages.data.myApi.categories;
    const addresses = pages.data.myApi.collectionQueryAddresses;
    const main = process.env.ROOT_CATEGORIES.split(',');
    main.forEach((type) => {
        categories.forEach((row) => {
            if (row.root && row.root.title === type &&
                row.parent.title === type) {
                const parentTitle = urlWriter(row.title);
                createPage({
                    path: `/search/france/${urlWriter(type)}/${parentTitle}`,
                    component,
                    context: {
                        categoryParent: row.id,
                        categories,
                        addresses,
                        city: null,
                        categoryId: null,
                        first: 10,
                        after: null,
                        isActive: true,
                        isDeleted: false,
                    },
                });
                categories.forEach((row1) => {
                    if (row1.parent && row1.parent.title === row.title) {
                        createPage({
                            path: `/search/france/${urlWriter(type)}/${parentTitle}/${
                                urlWriter(row1.title)}`,
                            component,
                            context: {
                                categoryParent: null,
                                categories,
                                addresses,
                                city: null,
                                categoryId: row1.id,
                                first: 10,
                                after: null,
                                isActive: true,
                                isDeleted: false,
                            },
                        });
                    }
                });
            }
        });
    });
    addresses.forEach((address) => {
        const place = address.city.replace(' ', '-').toLowerCase();
        categories.forEach((cat) => {
            if (cat.root && cat.root.title === cat.parent.title) {
                const catTitle = urlWriter(cat.title);
                createPage({
                    path: `/search/${urlWriter(place)}/${urlWriter(cat.root.title)}/${catTitle}`,
                    component,
                    context: {
                        categoryParent: cat.id,
                        categories,
                        addresses,
                        city: address.city,
                        categoryId: null,
                        first: 10,
                        after: null,
                        isActive: true,
                        isDeleted: false,
                    },
                });
            }
            if (cat.root && cat.root.title !== cat.parent.title) {
                const catParentTitle = urlWriter(cat.parent.title);
                createPage({
                    path: `/search/${urlWriter(place)}/${urlWriter(cat.root.title)}/${catParentTitle}/${urlWriter(cat.title)}`,
                    component,
                    context: {
                        categoryParent: null,
                        categories,
                        addresses,
                        city: address.city,
                        categoryId: cat.id,
                        fist: 10,
                        after: null,
                        isActive: true,
                        isDeleted: false,
                    },
                });
            }
        });
    });
},
// ***** BELOW DOES NOT WORK *****
onCreatePage: async ({ page, actions }) => {
    const { createPage } = actions;
    const component = path.resolve('./src/templates/search.js');
    if (page.path.match(/^\/search\/[-a-z]+\/[-a-z]+\/[-a-z]+\/[-a-z]+/)) {
        page.matchPath = '^\/search\/[-a-z]+\/[-a-z]+\/[-a-z]+\/[-a-z]+';
       page.component = component;
        // Update the page.
        createPage(page);
    }
},
};

Environment (if relevant)

System: OS: macOS 10.15.5 CPU: (4) x64 Intel® Core™ i5-4570S CPU @ 2.90GHz Shell: 5.7.1 - /bin/zsh Binaries: Node: 12.16.0 - /usr/local/bin/node npm: 6.13.4 - /usr/local/bin/npm Languages: Python: 2.7.16 - /usr/bin/python Browsers: Chrome: 83.0.4103.106 Safari: 13.1.1 npmPackages: gatsby: ^2.20.7 => 2.20.28 gatsby-cli: ^2.12.34 => 2.12.34 gatsby-image: ^2.2.33 => 2.3.4 gatsby-link: ^2.2.24 => 2.3.4 gatsby-plugin-create-client-paths: ^2.1.17 => 2.2.3 gatsby-plugin-eslint: ^2.0.8 => 2.0.8 gatsby-plugin-manifest: ^2.3.3 => 2.3.6 gatsby-plugin-material-ui: ^2.1.6 => 2.1.6 gatsby-plugin-offline: ^3.0.22 => 3.1.4 gatsby-plugin-react-helmet: ^3.1.15 => 3.2.4 gatsby-plugin-robots-txt: ^1.5.0 => 1.5.0 gatsby-plugin-sass: ^2.1.23 => 2.2.3 gatsby-plugin-sharp: ^2.5.3 => 2.5.6 gatsby-source-filesystem: ^2.1.38 => 2.2.4 gatsby-source-graphql: ^2.1.24 => 2.4.2 gatsby-transformer-sharp: ^2.4.2 => 2.4.6 npmGlobalPackages: gatsby-cli: 2.12.34

File contents (if changed)

gatsby-config.js: N/A package.json: N/A gatsby-node.js: N/A gatsby-browser.js: N/A gatsby-ssr.js: N/A

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

I just posted something in Discord regarding this exact issue on my project. We’ve been in development for over a year with hopes that a “real” solution would come forward but since this issue was just closed with no real explanation, Im assuming it has not been addressed. Maybe I am crazy but this seems like a pretty common flow for applications that generate pages based on data provided by users.

The last time around I was told to use the solution in this issue, however hacking the 404 page doesn’t seem like a scalable solution, especially with Gatsby trying to pave a way for websites built with React. Millions in funding raised, countless promises in “re-defining” web development and hacking a 404 page is the best solution? I can only imagine that tons of developers have evaluated Gatsby and decided against it because of this exact reason.

Essentially our app allows users to create public profiles each with their own slug. What ends up happening is as soon as they create it, they are given the unique URL, but if they try to access it a 404 follows since that page does not exist in the Gatsby lifecycle. After implementing the “solution” above, we now have to use our 404 page to match a URL and decide whether or not to serve the public profile. My concern is that the browser still gets 404 code from the server and in the event a Search Engine bot hitting this page before a build has occurred they too would get a 404 and most likely abort crawling the page.

Any help/guidance on this issue would be greatly appreciated. We are about to move into production and I would like to have a working, scalable solution in place as soon as possible.