Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow redirects from same path when they have different options #18220

Closed
bsonntag opened this issue Oct 6, 2019 · 11 comments · Fixed by #19048
Closed

Allow redirects from same path when they have different options #18220

bsonntag opened this issue Oct 6, 2019 · 11 comments · Fixed by #19048

Comments

@bsonntag
Copy link
Contributor

bsonntag commented Oct 6, 2019

Description

I'm creating a website with multiple locales and I'd like to have different routes for each locale (e.g. /en/about and /pt/about). I also want the / route to redirect to the correct locale based on the user's browser language setting.
On Netlify this can be achieved with the following _redirects file:

/  /en/  302  Language=en
/  /pt/  302  Language=pt

I'm trying to generate this using createRedirects and gatsby-netlify-plugin like the documentation explains but it's generating only one redirect rule:

/  /en/  302  Language=en

Steps to reproduce

  1. Add gatsby-netlify-plugin to gatsby-config.js.
  2. Add the following createPages to gatsby-node.js:
exports.createPages = ({ actions }) => {
  const { createRedirect } = actions;

  createRedirect({
    fromPath: '/',
    toPath: '/en/',
    Language: 'en',
  });
  createRedirect({
    fromPath: '/',
    toPath: '/pt/',
    Language: 'pt',
  });
}
  1. Run gatsby build.

Expected result

The following _redirect file should be created on the public folder:

/  /en/  302  Language=en
/  /pt/  302  Language=pt

Actual result

The following _redirect file is created on the public folder:

/  /en/  302  Language=en

Environment

  System:
    OS: macOS 10.14.6
    CPU: (8) x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
    Shell: 5.7.1 - /usr/local/bin/zsh
  Binaries:
    Node: 10.15.3 - ~/.nvm/versions/node/v10.15.3/bin/node
    Yarn: 1.17.3 - /usr/local/bin/yarn
    npm: 6.11.3 - ~/.nvm/versions/node/v10.15.3/bin/npm
  Languages:
    Python: 2.7.16 - /usr/local/bin/python
  Browsers:
    Chrome: 77.0.3865.90
    Firefox: 69.0.1
    Safari: 13.0.1
  npmPackages:
    gatsby: ^2.15.29 => 2.15.29 
    gatsby-image: ^2.2.24 => 2.2.24 
    gatsby-plugin-flow: ^1.1.9 => 1.1.9 
    gatsby-plugin-google-analytics: ^2.1.20 => 2.1.20 
    gatsby-plugin-manifest: ^2.2.20 => 2.2.20 
    gatsby-plugin-netlify: ^2.1.17 => 2.1.17 
    gatsby-plugin-react-helmet: ^3.1.10 => 3.1.10 
    gatsby-plugin-react-svg: ^2.1.2 => 2.1.2 
    gatsby-plugin-sharp: ^2.2.28 => 2.2.28 
    gatsby-plugin-sitemap: ^2.2.16 => 2.2.16 
    gatsby-plugin-styled-components: ^3.1.8 => 3.1.8 
    gatsby-source-filesystem: ^2.1.29 => 2.1.29 
    gatsby-transformer-sharp: ^2.2.20 => 2.2.20 
@bsonntag
Copy link
Contributor Author

bsonntag commented Oct 6, 2019

From my understanding this is happening because redirects with the same fromPath are being ignored to prevent duplicates:

// Add redirect only if it wasn't yet added to prevent duplicates
if (!fromPaths.has(fromPath)) {
fromPaths.add(fromPath)
state.push(action.payload)
}

Is there a way to prevent duplicates while allowing redirects with the same fromPath?

@me4502
Copy link
Contributor

me4502 commented Oct 7, 2019

They're being done this way currently for performance. I'm not sure what would be the best way of achieving the same performance improvement due to the way JS's Maps and Sets work, in that they require a primitive type for comparisons. Unless the data is stringified as an object, but that adds extra JSON serialising / deserialising overhead.

@bsonntag
Copy link
Contributor Author

Could it leave to the user to set an id (like when creating a node)? When it's not present, it could still fallback to the fromPath as an identifier.

@bsonntag
Copy link
Contributor Author

Could it leave to the user to set an id (like when creating a node)? When it's not present, it could still fallback to the fromPath as an identifier.

I played around with this idea and have a working implementation. Should I open a PR or would something like this need an RFC first?

@me4502
Copy link
Contributor

me4502 commented Oct 26, 2019

My idea was to have it be a map of lists, where the key is the path. That way it can still do decent lookups, and acts as a hash collision when it finds duplicate redirects at a path.

@bsonntag
Copy link
Contributor Author

What would be on the lists? The toPaths? So a redirect would be a duplicate if its fromPath and toPath already exist?

@pieh
Copy link
Contributor

pieh commented Oct 26, 2019

Keys in fromPaths set don't have to be just fromPath - we could generate some key based on full redirect body (not only just on fromPath). We could use createContentDigest to generate hash from redirect object and store that

For reference - This was initial implementation:

      if (!state.some(redirect => _.isEqual(redirect, action.payload))) {
        // Add redirect only if it wasn't yet added to prevent duplicates
        return [...state, action.payload]
      }

and it was allowing for multiple redirects using same fromPath, not quite sure why this was changed (I don't mean implementation to use Set for perf - that's fine, just the behaviour one is a question)

@bsonntag
Copy link
Contributor Author

One problem: createContentDigest returns different results for objects that are equal (when compared with _.isEqual) but have different property orders. Failing test that shows this:

  it(`returns equal content digests for objects with different property order`, () => {
    expect(createContentDigest({ a: 'a', b: 'b' })).toEqual(createContentDigest({ b: 'b', a: 'a' }))
  })

This means that the following code would generate two redirect entries with this approach:

  createRedirect({
    fromPath: '/',
    toPath: '/en/',
  });
  createRedirect({
    toPath: '/en/',
    fromPath: '/',
  });

adamhammes pushed a commit to adamhammes/kijiji-housing that referenced this issue Oct 26, 2019
Technically, "/" is not a page on this site. All pages are instead
prefixed by "fr" or "en" and I've set up redirects from "/" to the
appropriate language (this applies to all pages, not just the home
page).

Unfortunately, there seems to have been a regression in
gatsby-plugin-netlify. As brought up in
gatsbyjs/gatsby#18220, if multiple redirects
are created from the same page, all but the latest will be ignored, even
if the redirects have different options.  Previously, I was creating the
following redirects for each page:

    /quebec/  /en/quebec/  302
    /quebec/  /fr/quebec/  302 Language=fr

This let me redirect people to French/English based on their language
header. However, because of the linked issue, this is no longer
possible - only the French redirect is being generated. This is
effectively breaking redirects for all non-French speakers.

As a temporary fix, I am going to give up on sniffing the user language
and instead always redirect to the default language.
@me4502
Copy link
Contributor

me4502 commented Oct 27, 2019

When I made the PR that changed redirects to this initially, I tested by serialising the objects to string (and also manually by creating a string). It MASSIVELY impacted performance by turning the entire entry into a string.

Having a map of fromPath -> list of entire redirect entries makes the most sense, as performance won’t be impacted too much (unless you have a lot of redirects from the same path)

@bsonntag
Copy link
Contributor Author

Oh, I see. So the check for duplicates would use _.isEqual, but it would only iterate over entries with the same fromPath.

@bsonntag
Copy link
Contributor Author

I updated #19048 to do this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants