Open pi0 opened 10 months ago
How would you utilize this if the canonical URL is determined at runtime, such as in a multi-tenant environment with dynamic subdomains?
How would you utilize this if the canonical URL is determined at runtime, such as in a multi-tenant environment with dynamic subdomains?
useRequestURL
utility or have your own composable with multi-tanent specific logic handling
- If there is one dynamic canonical per instance, via runtime config
Since we only build once, setting an env variable isn't an option
- If it is variable across all requests, i think you should rely on the current
useRequestURL
utility or have your own composable with multi-tanent specific logic handling
I've tried this, but even when setting the varies
option, the Nitro server does not always receive the correct URL from useRequestURL
:
localhost
There doesn't seem to be a consistent way
setting an env variable isn't an option
You set environment variables when running instance. It is possible to set them after-build actually, hence the benefit of using of runtimeconfig.
I've tried this, but even when setting the varies option, the Nitro server does not always receive the correct URL from useRequestURL:
would be worth to track with another issue if you don't mind to make a minimal reproduction of your setup so i can investigate 🙏🏼
It is possible to set them after-build actually, hence the benefit of using of runtimeconfig.
Have a simple of example of how you would set the runtimeConfig value after build, e.g. when the app first initializes and grab the browser's hostname, etc.?
would be worth to track with another issue if you don't mind to make a minimal reproduction of your setup so i can investigate
Here's a new issue with reproduction and a deployed preview: https://github.com/unjs/nitro/issues/2388
I would also like to chime in here, I've been spending the better half of 2 days now trying to work around this problem.
What we do:
We are using Nuxt to serve two distinct projects that overlap quite a lot and as such they share a lot of components. As it seemed much simpler we decided to let it all be handled by one Nuxt instance with some trickery on the rewrite level of the HTTP server (in our case Caddy).
How we achieve this is by having 2 distinct folders in the pages directroy of Nuxt one (for this example) called foldera
another called folderb
. We achieve this by having a router.options.ts
file that rewrites the path based on the fact if it is a subdomain or not.
app/router.options.ts
import type {RouterOptions} from "@nuxt/schema";
import {useRequestHeader, useRequestURL} from '#imports';
import type {RouteRecordRaw} from "vue-router";
const rewritePrefixRoute = (route: RouteRecordRaw, prefix: string) => {
if (route.path.startsWith(prefix)) {
return {
...route,
path: route.path.replace(prefix, ""),
};
}
return route;
}
export default <RouterOptions>{
routes: (routes) => {
let hostname = useRequestHeader('X-Requested-Host') ?? '';
console.info('X-Requested-Host:', hostname);
if (hostname == null || hostname == '') {
hostname = useRequestURL()['hostname'];
}
console.info('Rewriting routes for hostname:', hostname);
const domainArray = ['domain.local'];
const rootDomain = domainArray.find(domain => hostname.endsWith(domain));
if (!rootDomain) {
return routes;
}
const subdomain = hostname.substring(0, hostname.indexOf(rootDomain) - 1);
if (hostname === rootDomain && subdomain === '') {
console.info('Rewriting /foldera folder to root');
return routes.map((route) => rewritePrefixRoute(route, '/foldera'))
}
console.info('Rewriting /uvw folder to root');
return routes
.map((route) => rewritePrefixRoute(route, '/folderb'))
},
};
What works:
All requests on the main domain work without a problem (foldera
)
What doesn't work: All request on the subdomains fail as they seem to prefix the entire URL in front of the resolving route.
So far, I have been able to get caddy to forward the correct headers and have added the described nuxt config:
routeRules: {
'/**': {
cache: { swr: true, varies: ['host', 'x-forwarded-host'] }
}
}
Caddyfile
*.{$WWW_SERVER_NAME} {
@isFile {
path *.css *.js *.Vue *.html
path /*
}
@isNotFile {
not path *.css *.js *.Vue *.html
path /*
}
log
redir /foldera* {scheme}://{labels.2}.{labels.1}.{labels.0}/404
rewrite @isFile {scheme}://{labels.2}.{labels.1}.{labels.0}{uri}
rewrite @isNotFile {scheme}://{labels.2}.{labels.1}.{labels.0}{path}
reverse_proxy {
to {$WWW_SERVICE_NAME:nuxtjs}:{$WWW_SERVICE_PORT:80}
# Note that I tried this with or without subdomains
header_up Host {labels.1}.{labels.0}
header_up X-Forwarded-Host {labels.1}.{labels.0}
header_up X-Requested-Host {labels.2}.{labels.1}.{labels.0}
}
}
{$WWW_SERVER_NAME} {
log
redir /folderb* {scheme}://{labels.1}.{labels.0}/404
reverse_proxy {
to {$WWW_SERVICE_NAME:nuxtjs}:{$WWW_SERVICE_PORT:80}
header_up Host {labels.1}.{labels.0}
header_up X-Forwarded-Host {labels.1}.{labels.0}
header_up X-Requested-Host {labels.1}.{labels.0}
}
}
I have also added logging to trace what is actually happening using:
/server/plugins/logging.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('request', (event) => {
console.info(
'Incoming request for URL',
event.node.req.url,
event.node.req.originalUrl,
JSON.stringify(event.node.req.rawHeaders)
)
})
nitroApp.hooks.hook('beforeResponse', (event) => {
console.info(
'Sending response with status',
event.node.res.statusCode,
event.node.req.url,
event.node.req.originalUrl,
JSON.stringify(event.node.req.rawHeaders),
JSON.stringify(event.node.res.getHeaders())
)
})
})
This is what the logs show:
main domain (foldera):
Incoming request for URL / / ["Host","domain.local","User-Agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding","gzip, deflate, br, zstd","Accept-Language","en-US,en;q=0.9,nl;q=0.8","Cache-Control","no-cache","Cookie","i18n_redirected=nl","Pragma","no-cache","Priority","u=0, i","Sec-Ch-Ua","\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"128\"","Sec-Ch-Ua-Mobile","?0","Sec-Ch-Ua-Platform","\"Linux\"","Sec-Fetch-Dest","document","Sec-Fetch-Mode","navigate","Sec-Fetch-Site","none","Sec-Fetch-User","?1","Upgrade-Insecure-Requests","1","X-Forwarded-For","172.19.0.1","X-Forwarded-Host","domain.local","X-Forwarded-Proto","https","X-Requested-Host","domain.local"]
2024-09-06T10:33:20.362413727Z X-Requested-Host:
2024-09-06T10:33:20.362576853Z Rewriting routes for hostname: domain.local
2024-09-06T10:33:20.362601149Z Rewriting /foldera folder to root
2024-09-06T10:33:20.378597587Z App: Current locale: nl
sub domain(folderb):
Incoming request for URL https://subdomain.example.local/ https://subdomain.example.local/ ["Host","example.local","User-Agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding","gzip, deflate, br, zstd","Accept-Language","en-US,en;q=0.9,nl;q=0.8","Cache-Control","no-cache","Cookie","i18n_redirected=nl","Pragma","no-cache","Priority","u=0, i","Sec-Ch-Ua","\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"128\"","Sec-Ch-Ua-Mobile","?0","Sec-Ch-Ua-Platform","\"Linux\"","Sec-Fetch-Dest","document","Sec-Fetch-Mode","navigate","Sec-Fetch-Site","none","Sec-Fetch-User","?1","Upgrade-Insecure-Requests","1","X-Forwarded-For","172.19.0.1","X-Forwarded-Host","example.local","X-Forwarded-Proto","https","X-Requested-Host","subdomain.example.local"]
2024-09-06T10:30:33.575387614Z Incoming request for URL /__nuxt_error?url=https:%2F%2Fsubdomain.example.local%2F&statusCode=404&statusMessage=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&message=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&stack /__nuxt_error?url=https:%2F%2Fsubdomain.example.local%2F&statusCode=404&statusMessage=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&message=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&stack ["host","example.local","user-agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","accept-encoding","gzip, deflate, br, zstd","accept-language","en-US,en;q=0.9,nl;q=0.8","cache-control","no-cache","cookie","i18n_redirected=nl","pragma","no-cache","priority","u=0, i","sec-ch-ua","\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"128\"","sec-ch-ua-mobile","?0","sec-ch-ua-platform","\"Linux\"","sec-fetch-dest","document","sec-fetch-mode","navigate","sec-fetch-site","none","sec-fetch-user","?1","upgrade-insecure-requests","1","x-forwarded-for","172.19.0.1","x-forwarded-host","example.local","x-forwarded-proto","https","x-requested-host","subdomain.example.local","x-nuxt-error","true"]
2024-09-06T10:30:33.576433488Z X-Requested-Host: subdomain.example.local
2024-09-06T10:30:33.576446121Z Rewriting routes for hostname: subdomain.example.local
2024-09-06T10:30:33.576449969Z Rewriting /folderb folder to root
2024-09-06T10:30:33.581293825Z Sending response with status 200 /__nuxt_error?url=https:%2F%2Fsubdomain.example.local%2F&statusCode=404&statusMessage=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&message=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&stack /__nuxt_error?url=https:%2F%2Fsubdomain.example.local%2F&statusCode=404&statusMessage=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&message=Cannot+find+any+route+matching+https:%2F%2Fsubdomain.example.local%2F.&stack ["host","example.local","user-agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","accept-encoding","gzip, deflate, br, zstd","accept-language","en-US,en;q=0.9,nl;q=0.8","cache-control","no-cache","cookie","i18n_redirected=nl","pragma","no-cache","priority","u=0, i","sec-ch-ua","\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"128\"","sec-ch-ua-mobile","?0","sec-ch-ua-platform","\"Linux\"","sec-fetch-dest","document","sec-fetch-mode","navigate","sec-fetch-site","none","sec-fetch-user","?1","upgrade-insecure-requests","1","x-forwarded-for","172.19.0.1","x-forwarded-host","example.local","x-forwarded-proto","https","x-requested-host","subdomain.example.local","x-nuxt-error","true"] {"vary":"Accept-Encoding","content-type":"text/html;charset=utf-8","x-powered-by":"Nuxt"}
At this point, I am wondering what I could do and given @adamdehaven worked on this I was wondering if you managed to make it work or if more work is required in Nitro / Nuxt.
Small update I managed to debug my way to the following error:
onError H3Error: Cannot find any route matching https://example.domain.local/.
Update
It all boils down to the following, the node req url property with the main domain is /
while for subdomains contains the entire URL. I have no clue why this happens, though. But is part of the entire node request object from the start, it seems. I am now looking at ways to overwrite this through one of the available hooks.
get path() {
return this._path || this.node.req.url || '/'
}
2024-09-09 - Update
Based on another change suggested for H3 about sanitizing the URL (https://github.com/unjs/h3/pull/765) I forked the repo and made my own change to fix this and resolve it:
This allows me to do the following in Nuxt:
const urlRegex = /^(?:(?:https|http)\:\/\/)?(?:[a-z_-]*\.)?(?:[a-z_-]*\.)(?:local|site|nl)(.*)$/
nitroApp.hooks.hook('request', (event) => {
console.debug(
'Incoming request for URL',
event.node.req.url,
event.node.req.originalUrl,
JSON.stringify(event.node.req.rawHeaders)
)
const url = event.node.req.url || ''
const matches = url.match(urlRegex)
if (matches) {
const match = matches[1] || '/'
console.debug('URL matches regex pattern, setting path to', match)
event.node.req.url = match
event._path = match
}
})
})
ref https://github.com/nuxt/nuxt/issues/24813
Today, we expose
useRequestURL()
utility to access the current request URL however it can be tricky in two situations:varies
(which can also lead to cache leaks)Proposal:
app.canonicalURL
in runtimeConfig for env support + app config for dynamic behavior support (prefer app)useRequestURL()
useCanonicalURL()
(suggested by @Atinux) that falls back touseRequestURL()
if the user hasn't configured a canonical URL and fails for SSG if not provided