-
Notifications
You must be signed in to change notification settings - Fork 135
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
Fix: react resolution for interception, parallel and group routes #155
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@conico974 This appears to be broken in latest 13.4.10 😢 |
// Get route pattern | ||
if (rewrite) { | ||
route = { | ||
page: rewrite.destination, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have dynamic routes, the destination is /:dynamic
, while the routes manifest is /[dynamic]
, and thus isApp
will fail and cause 404 errors.
rewrite.destination.replace(/:([^\/]+)/g, '[$1]')
f866acf
to
5abab09
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Fixes a few issues others have reported w/ the query stuff too 👍
I'll test this in a couple hours.
} | ||
// Do we need to replace anything else here? | ||
const rewrittenUrl = | ||
rewrite?.destination.replace(/:[^\/]+/g, '[$1]') ?? rawPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that should be good, I've checked the Vercel deployed app and they contain the (.)
in the route (so we don't need to replace the groups; they may change that in the future for all we know)
} | ||
// Do we need to replace anything else here? | ||
const rewrittenUrl = | ||
rewrite?.destination.replace(/:[^\/]+/g, '[$1]') ?? rawPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rewrite?.destination.replace(/:[^\/]+/g, '[$1]') ?? rawPath; | |
rewrite?.destination.replace(/:([^\/]+)/g, '[$1]') ?? rawPath; |
You forgot the grouping ()
, otherwise, it'll replace the word with a literal $1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah i noticed that, testing this right now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wasn't working finally, we need to replace the rawPath
here, i used https://github.com/pillarjs/path-to-regexp. This is the lib they use internally inside next
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I'm getting 502's on prefetch now
Invoke Error {"errorType":"TypeError","errorMessage":"Expected \"0\" to be a string","stack":["TypeError: Expected \"0\" to be a string"," at file:///var/task/packages/app/index.mjs:27:37895"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's likely that it comes from here https://github.com/conico974/open-next/blob/b3060a37960e4a1305fe82d125d5003c7e0897be/packages/open-next/src/adapters/routing.ts#L74-L85. Could you try adding some log here to see what could be the cause
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated my comment w/ additional info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should have read the docs a little more... https://github.com/pillarjs/path-to-regexp#unnamed-parameters. So in compile
it treats (.)
as an unnamed parameter, and so it expects it to be present when we use it in toDestination
. see https://github.com/pillarjs/path-to-regexp/blob/1cbb9f3d9c3bff97298ec45b1bb2b0beb879babf/src/index.spec.ts#L2892
We actually need the interception path (i.e. (.)|(..)|(...)
) to be returned from our function so that it return the correct route. I think we need to escape those interception path for when we use toDestination
and then replace those escaped path with (.)|(..)|(...)
so that we return the expected path
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm but isn't that (.) regex, and not directory paths?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a util function in utils.ts:
const replacePathRegex = /(\(?\w+\)?\/)?\(\.{2}\)\/?/;
export function resolvePaths(path: string) {
const rootIndex = path.indexOf("(...)");
// If path contains (...), replace everything before it, including itself
if (~rootIndex) {
path = path.substring(rootIndex + 5);
}
// Remove (.), since that resolves to nothing
path = path.replace(/\(\.\)\/?/g, "");
// Resolves each (..) path by consuming the path before it
// eg: /page/(..)[dynamic] => /[dynamic]
while (path.match(/\(\.{2}\)/)) {
path = path.replace(replacePathRegex, "");
}
return path;
}
Then in routing.ts, I resolved the source & destination:
const toDestination = compile(resolvePaths(rewrite?.destination) ?? "");
const fromSource = match(resolvePaths(rewrite?.source) ?? "");
This works pretty well (for now, needs more testing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nvm, the modal's param.title is messed up for some reason, it has %3Atitle
The client side params is:
author: "%3Aauthor"
title: "%3Atitle"
The object returned has url: url: '/page/(.):author/:title?_rsc=3sl5w',
if (isUsingParams) { | ||
rewrittenUrl = toDestination(params); | ||
} | ||
rewrittenUrl = rewrite.destination; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be inside an else
statement, otherwise the toDestination
gets overridden. Hmmm, modal breaks again w/ the else statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ho come on, how could i forget that..
Ok at least this way it makes more sense to me, i couldn't understand how your fix could resolve the issue, i'm pretty sure we need the (.)
in the final url.
I've just tried something and it seems to work
I created these 2 functions
export function escapeRegex(str: string) {
let path = str.replace(/\(\.\)/g, '%1');
path = path.replace(/\(\.{2}\)/g, '%2');
path = path.replace(/\(\.{3}\)/g, '%3');
return path;
}
export function unescapeRegex(str: string) {
let path = str.replace(/%1/g, '(.)');
path = path.replace(/%2/g, '(..)');
path = path.replace(/%3/g, '(...)');
return path;
}
And for the rewrite part
const toDestination = compile(
escapeRegex(rewrite?.destination ?? '') ?? ''
);
const fromSource = match(escapeRegex(rewrite?.source) ?? '');
const _match = fromSource(rawPath);
if (_match) {
const { params } = _match;
const isUsingParams = Object.keys(params).length > 0;
if (isUsingParams) {
rewrittenUrl = unescapeRegex(toDestination(params));
console.log('rewrittenUrl1', rewrittenUrl);
} else {
rewrittenUrl = rewrite.destination;
}
console.log('rewrittenUrl2', rewrittenUrl);
Of course those escape character are pretty bad, but for testing it's ok, i need to figure out some escape that will not mess things up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have an extremely ugly hack to get the correct params from the modal:
In routing.ts:
let params: { [key: string]: string } = {};
if (rewrite) {
const fromSource = match(resolvePaths(rewrite?.source) ?? "");
const _match = fromSource(rawPath);
if (_match) {
params = _match.params as any;
rewrittenUrl = rewrite.destination;
}
}
return {
rawPath: rewrittenUrl,
url: `${rewrittenUrl}${urlQueryString ? `?${urlQueryString}` : ""}`,
params,
__rewrite: rewrite,
};
In server-adapter:
const { rawPath, url, params } = handleRewrites(
internalEvent,
routesManifest.rewrites
);
....
const body = ServerResponse.body(res)
.toString(encoding)
.replace(/%3A([^"]+)/g, (_, match: string) => {
return params[match] || `%3A${match}`;
});
But there's gotta be a correct way to do this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, let me give that a go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Modal interception + client parameters works now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would fromSource
response ever need to be unescapeRegex'd?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes otherwise next-server
will not find the correct interception route path.
I'll change the escape character to something like_µ1_
, it should be safe enough so that user will not have these in the url
I tried rewrite and redirect. Redirect works but
This works locally but 404s on deployed app. |
It might be that it's generated as I tested mine this way and it works for me, even with dynamic path rewrites: () => ({
beforeFiles: [
{
source: '/shouldBeRewritten',
destination: '/',
},
{
source: '/shouldBeRewritten/:path',
destination: '/:path',
},
],
}), |
Ah yes, it's generated as
|
cleanedKey = cleanedKey.replace(/\/\((?!\.)[^\)]*\)/g, ''); | ||
|
||
// Remove /page suffix | ||
cleanedKey = cleanedKey.replace(/\/page$/g, ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@conico974 I'm using app/api/something/route.tsx
, but the key still contains app/api/something/route
, should we also remove the route
? The nextjs codebase deletes the last /<thing>
from the key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically we should, but I don't think it matters that much. This is used only to modify react resolution so that your page use the same version of react as next. I don't think they do anything related to react on route
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some people are having rewrite issues in next config. I think we should concat before/after files and fallback unless there's a gotcha w/ it. See: https://discord.com/channels/983865673656705025/1138321896816001086
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is actually a gotcha, https://nextjs.org/docs/app/building-your-application/routing/middleware#matching-paths. We should follow this execution order.
I think in order to do that we'll have to merge this with your middleware fix, i'll try to come up with something later today
12512bf
to
1fc4ec5
Compare
Right now with app dir, interception, parallel and group routes doesn't work correctly.
This PR should solve this