I'm setting up redirects for a client in Next.js. We have a configuration like so inside next.config.js:
{
source: '/page.aspx:urlstr*',
destination: 'https://someurl.com/page.aspx:urlstr*',
permanent: true,
},
The actual URLs hitting this will have a long URL string like so:
page.aspx?woXxJNrRIMKVC109awwTopP+k2NmkvXf+MzijTEc3zIZ3pf4n+Yknq
This is being URL encoded to be:
https://someurl.com/page.aspx?woXxJNrRIMKVC109awwTopP%20k2NmkvXf%20MzijTEc3zIZ3pf4n%20Yknq
The old server hosting these pages at the destination can't handle the URL encoded query string. Is there a way to force Next.js to not parse it?
So the solution was in fact to use the Next.js middleware feature:
https://nextjs.org/docs/advanced-features/middleware
It's a little buggy though. The docs say to add middleware.js to the same level as the pages directory, but you actually need to add _middleware.js inside the pages directory.
Also the matches feature does not seem to work for me at all, so here's my solution:
import { NextResponse } from 'next/server'
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/page.aspx')) {
let url = new URL(request.url);
return NextResponse.redirect(`https://someurl.com${url.pathname}${url.search}`)
}
}
Related
Minimal repro site: https://nextjs-paths-issue.vercel.app/
Minimal repro code: https://github.com/saadq/nextjs-encoding-issue
Home page
Food page
I am trying to iterate through an array of food objects and create static pages for each one based on its title. This works for most of the foods, but if the food title contains a /, then navigating to the page (such as the "Nice strawberry/kiwis dessert" page) will throw a 404.
In the home page, I encode the URL when I create the Link and then in the getStaticPaths function, I create the paths using the same encoded link. However, it doesn't seem to work when deployed.
The page does work locally when running npm run dev, but it seems that in the actual output build there are issues. Is there something I can do to allow paths with encoded slashes to work?
Home page
const HomePage: NextPage = () => (
<>
<h1>Home</h1>
<ul>
{foods.map((food) => (
<li key={food.title}>
<Link href={`/food/${encodeURIComponent(food.title)}`}>
{food.title}
</Link>
</li>
))}
</ul>
</>
)
Food page
export const getStaticProps: GetStaticProps<Props, Params> = (ctx) => {
const title = ctx.params?.foodTitle as string
const food = foods.find((food) => food.title === title) as Food
return {
props: {
food
}
}
}
export const getStaticPaths: GetStaticPaths = () => {
const paths = foods.map((food) => `/food/${encodeURIComponent(food.title)}`)
return {
paths,
fallback: false
}
}
const FoodPage: NextPage<Props> = ({ food }) => {
return (
<>
<Link href='/'>Go Back</Link>
<h1>{food.title}</h1>
<h2>Amount: {food.amount}</h2>
</>
)
}
export default FoodPage
The example I posted here is a bit contrived, for my actual app I was able to get it to work by using fallback: 'blocking'. It's not a totally exportable static website anymore unfortunately, but I only have a few pages that will run into this issue so it's fine that a few of them will have a slight loading time from the server.
https://nextjs.org/docs/basic-features/data-fetching
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
An old thread, but I've spent many hours looking for a solution so I'd like to share.
In order to get links in the format /categories/page in getStaticPath, and at the same time to use the same component in the /category/ and in the /category/page/, but with different parameters, you should use the page format [...category].js. This allows you to pass parameters in getStaticPaths as an array:
params: {
category: [category, page],
}
In result it will generate page category/page and still have fallback: false.
Trying to add '/' directly to the path (as you did) creates html pages category%2fpage.html.
More details about static paths
And about catch all routes
Ofc, because you have / in the path. %2F symbol representing /
Just make custom function for path encoding and use it
static stringToParamUrl(input: string) {
let url = input.normalize("NFD").toLowerCase().replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9-_]/g, "-")
return url
}
Modifications:
"I create the paths using the same encoded link. However, it doesn't seem to work when deployed." if it's worked it is not correct. Your other links are working exept those containing "/". "/" is a reserved character for the URL. In next js it's working as "separator".
Also (about Link):
You need to have <a> tag in Link
<Link><a>something</a></Link>
Also (about fallback):
Fallback is not changing the logic of URLs. Using fallback will generate static page on the "first demand" - nothing about URL not working.
You had no errors during deployment time because you didn't create static page (fallback false)
What you need:
"I am trying to iterate through an array of food objects and create static pages for each one based on its title." = you need to add to your database a field called "URL" or something like this, and iterate elements based on URL *you previously generated while creating your array) and not name, because:
You need to have "unique" URL (to get data from your api via [pid] parameters or something)
You need to avoid reserved and forbidden symbols in URL
It's better to prettify url. ( I shared a function with you)
Maybe I didn't understand your question correctly
In our VueJS app I have a URL that is returned from an API that points to amer.example.com/put/stuff that I need to PUT some data.
Obviously when I do this PUT I get CORS errors. To remedy this I have a devServer proxy setup that says to reroute all /put/stuff to go through example.com like this:
devServer: {
port: 9034,
proxy: {
'/put/stuff': {
target: 'amer.example.com',
},
},
},
Voila, things start working again.
However, the URL that is returned from the API is dynamic and can be pointing to yet another region like EMEA, emea.example.com/put/stuff but everything else in the URL is the exact same.
How can I make the proxy dynamic so that it will go to amer.example.com, emea.example.com, or any other region based on the URL I get back from another API.
I have no control over the other API and how the URL that is returned is shaped.
The only way that I can think of to do this would be ugly, but it should work.
TLDR
Insert the region into the path of the URL you get back from the API, and then look for that path in the proxy. Finally use pathRewrite in your proxy to remove it from the URL.
Longer explanation
Create a new function in your methods that inserts the region into the URL path, like this:
methods: {
insertRegionIntoUrlPath(urlStr) {
const url = new URL(urlStr);
// Grab the region from the url, if it includes 'https://' then update the substring obviously).
const regionPath = `/${url.hostname.substring(0,4)}`;
// Set the path to now include the region in the beginning
url.pathname = `${regionPath}${url.pathname}`;
// Return the full URL that now contains the region: `https://amer.example.com/amer/put/stuff`
return url.href;
},
}
You would then use this function wherever that PUT is happening to add the region into the URL path.
I've made an assumptions that your region is always 4 characters long, if that could be different then just use some regex to pull the subdomain out.
Then in your proxy setup you can now target the specific region paths you want and remove the part of the path you added, like this:
module.exports = {
devServer: {
proxy: {
'/amer': {
target: 'https://amer.example.com',
pathRewrite: {'^/amer' : ''} // Removing `/amer` from the path
},
'/emea': {
target: 'https://emea.example.com/',
pathRewrite: {'^/emea' : ''} // Removing `/emea` from the path
}
}
}
};
So now what you have done is got a URL from the API, added the region into the path and made a PUT request and finally the proxy picks up these requests because they will match the region paths like /amer and then we simply have the proxy remove that part from the URL before the request is sent off.
I am looking for way how to create a export file for external application, which accepts diacritics encoding only in Windows-1252. User should be able to download file through web application and import it to external application. I am using nodejs at backend.
Problem is that nodejs do not support encodings not even similar to 1252, so special characters like ľščťžýáíé are problem. Is there some workaround or way how to create file at frontend(after AJAX request) in required encoding?
EDIT:
Encoding was poor specificated by application owner. As #robertklep said, windows-1252 was bad encoding. I tryed lot of different encodings and a proper one was a CP-1250. Using a iconv-lite I created this example solution.
total.js
const iconv = require('iconv-lite);
exports.install = function() {
F.route('/route/to/export', download, ['get']);
};
function download() {
var self = this;
var text = 'some important content on multiple lines';
var content = iconv.encode(text, 'CP1250');
return self.res.content(200, content, 'text/plain', true, {
'Content-Disposition': 'attachment; filename="export.txt"'
});
}
frontend
$('#btn-export').on('click', function(){
location.href = '/route/to/export';
});
This is working well for me, but can someone find out better solution for file transfer from backend?
In our app, we are serving static angular app files out of foobar.com/public/angular. The home page (foobar.com) is configured to serve foobar.com/public/angular/index.html. Thus, in our index file's <head>, we have:
<base href="http://foobar.com/public/angular">
However, during development, we use foobar.com/debug, which causes the server to use this instead:
<base href="http://foobar.com/public/angular-debug">
The server returns unprocessed (not minified, uglified, etc) versions of the files from this url. We are also using angular-route for our views.
TLDR:
We have a production route: foobar.com
We have a dev route: foobar.com/debug
We have a base tag: <base href="http://foobar.com/public/whatever">
We are using hash-bang urls for angular routes
The trouble is generating urls on the client side. As discussed in comments, Click here does not work because the resulting url contains the base tag, which the server rejects and looks odd since the user starts somewhere else: foobar.com/public/angular/#/myRoute). The best I could think of to create the desired url was this:
/**
* Returns current url after replacing everything after the hashbang with a new string.
*
* #param {string} s - string to change route with (e.g. 'asdf')
* #return {string} - new url e.g. http://google.com/foobar/#!/asdf
*/
makeRouteUrl = function(s) {
var root = $location.absUrl().match(/.*#!/);
if (root && (root.length > 0)) {
root = root[0];
return( root + s );
} else {
// no hashbang in path? Choose home page (not intended use)
return "/";
}
}
However, this just seems a little gross. Is there a function in angular that makes the above function unnecessary? I have looked for functions in $location, $route, etc., but nothing seems to handle our complex setup correctly. We need to ultimately get the url as a string, not navigate (we are supporting open in new tab, so need to used ng-href). Since Angular apps often use hashbang urls for routing, I figured there must be something that allows you the just change the route after the hashbang while still preserving what's in front of it.
I'm not entirely sure if I understand the problem...
$location.url('/stuff/after/hash?params=hey');
Or if you only want to set the path after #:
$location.path('/stuff/after/hash');
Is what I usually use. But you need to return the url without hash after this?
How about
window.location.origin + window.location.pathname
For the current url without hashes and parameters?
Have you considered using the UI Router library?
In general, it's excellent.
Specific to your question, the UrlMatcher service provides a method format that- unless I'm misunderstanding what you're asking- does what you're trying to do.
new UrlMatcher('/your/route/here').format(optionalNamedParameters);
// returns a formatted URL
I'm trying to do something pretty simple.
Here's the scenario:
I have a whole site working great with pushstate enabled browsers. The site works off the basis that the language is the "actual page" for instance:
/en/whatever/etc = index.en.php with with english headers and encoding & tweaks
/ar/whatever/etc = index.ar.php with with arabic headers and encoding & tweaks
/ru/whatever/etc = index.ru.php with with russian headers and encoding & tweaks
It's really slick and works nicely with pushstate as I mentioned. The problem is when I try to use the same router code with the hash alternative.
Backbone's router seems to want to do this:
/#/en/whatever/etc = bad because it's not based correctly
/#/ar/whatever/etc = and so on
what I want it to do is:
/en/#whatever/etc
/ar/#whatever/etc
/ru/#whatever/etc
..and so on
or even:
/en/#/whatever/etc
/ar/#/whatever/etc
/ru/#/whatever/etc
..and so on
But i can't find a way without tweaking backbones source to implement this. I'm kind of against changing backbone.js unless I really have to because of the futureproofing factor.
Anyone have any thoughts?
You need to use the root option in the Backbone History object:
Quote the docs:
If your application is not being served from the root url / of your domain, be sure to tell History where the root really is, as an option: Backbone.history.start({pushState: true, root: "/public/search/"})
You're going to have to do some code that happens on your landing page that looks at what language they're using, and then sends them to /[LANG CODE]/. Then on your server, you just map requests for /[LANG CODE]/ to your .html file.
In your HTML file look at the location.pathname variable, strip off the first /[LANG CODE]/ part and use that as your root value for the options hash.
var lang = "/"+_.first(location.pathname.split('/')+"/";
Backbone.history.start({pushState: true, root: lang}); // if you're using pushState, otherwise omit or set to false
This will cause your URLs to be:
/en/#whatever/etc
/ru/#whatever/etc