Masking a URL destination file using Rewrite in Multi language config - web

I'm trying to implement a rewrite that will mask the destination if it's '/dest' to '/new'
The Next.js documentation suggested the following
module.exports = {
async rewrites() {
return [
{
source: '/dest',
destination: '/new',
},
]
},
}
I'm having a hard time plugging it into my code which contains
rewrites: async () => nextI18NextRewrites(localeSubpaths),
publicRuntimeConfig: {
localeSubpaths,
},

I believe I understand what the issue is. Multiple rewrites of one request does not work, it simple takes the first matching rewrite. In my case it was { source: '/:lang(en)/:path*', destination: '/:path*' }.
So it works if I add my rewrite above ...nextI18NextRewrites(localeSubpaths) while also manually adding the localeSubpath for it, e.g.
module.exports = {
rewrites: async () => {
return [
{
source: '/en/gardening/london',
destination: '/services/gardening/london',
},
...nextI18NextRewrites(localeSubpaths),
];
},
publicRuntimeConfig: {
localeSubpaths,
},
};

Related

Vite plugin for SvelteKit: Generated files aren't added to the build output

I am currently working on another plugin, that should in the end generate the webmanifest and all images and splash screens needed for a PWA (minus the service worker). I am planning on making this a plugin for vite (rollup), with a special focus on sveltekit, because that's where I plan on using it.
I currently have this setup as a package that exports both mjs and cjs, and should for all I know have a working version to test with. Sadly, the output emitted using this.emitFiles doesn't appear in the build output, even though prior function returns an assetId that resolves to a URL.
Code
index.ts
import { Plugin } from 'vite'
import { PluginOptions } from './types.js'
import { readFileSync } from 'fs'
import { generateResizedWebpIcon, generateResizedJpegIcon } from './utils.js';
export default (options: PluginOptions): Plugin => {
const iconResolutions = [16, 48, 128, 512]
return {
name: 'vite-plugin-pwa',
async transformIndexHtml() {
// add images and manifest to build output
// generate icons and emit them, store the urls
const icon = readFileSync(options.image.src)
let icons = await Promise.all(iconResolutions.map(async res => {
const resolveID = this.emitFile({
type: 'asset',
name: `icon-${res}x${res}.webp`,
source: await generateResizedWebpIcon({...})
})
return {
type: 'image/webp',
sizes: `${res}x${res}`,
src: this.getFileName(resolveID)
}
}, this))
if (options.image.output?.jpeg) {
icons.push(...await Promise.all(iconResolutions.map(async res => {
const resolveID = this.emitFile({
type: 'asset',
name: `icon-${res}x${res}.jpeg`,
source: await generateResizedJpegIcon({...})
})
return {
type: 'image/jpeg',
sizes: `${res}x${res}`,
src: this.getFileName(resolveID)
}
}, this)))
}
const packageInfo = JSON.parse(readFileSync('package.json').toString())
const manifest = {
name: packageInfo.name || 'name',
description: packageInfo.description || 'description',
...options.manifest || {},
icons
};
const manifestUrl = this.getFileName(
this.emitFile({
type: 'asset',
name: 'manifest.json',
source: Buffer.from(JSON.stringify({
manifest
}))
})
)
// generate manifest with icons, save the url
// generate apple splashes and emit them, save the urls
// add links to manifest and apple meta tags
return [
{
tag: 'link',
attrs: {
rel: 'manifest',
href: manifestUrl
},
injectTo: 'head'
}
]
},
}
}
In this example, the <link rel="manifest" href="_app/manifest.webmanifest"> turns up in the html and chrome tries to fetch it. But the server returns a 404 Not Found code. It appears vite emits the file, but it is somehow overwritten by the sveltekit build process?
Does anyone know how to make this emit a file that also turns up in the final build output?

NextJS redirects not redirecting urls after define in next.config.js file

I tried to define redirects in my NextJS app.
but it is not working.
This is how I tried to do it in my next.config.js file:
const withImages = require('next-images')
const withPlugins = require("next-compose-plugins");
const optimizedImages = require("next-optimized-images");
module.exports = withPlugins(
[
[optimizedImages, {
inlineImageLimit: 512
}]
],
{
async redirects() {
return [
{
source: "/sales/guest/form",
destination: "/",
permanent: true
}
]
},
env:{
testEnvVar: 'vallll'
}
}
);
This is the documentation of how to do it:
https://nextjs.org/docs/api-reference/next.config.js/redirects
For redirects and rewrites to work properly in NextJs, you also need to ensure one more thing:
If you are using trailingSlash: true then your source paths must end with a slash.
{
source: '/old/:id/', // Notice the slash at the end
destination: '/new/:id',
},
Any other plugins or configurations that interfere with routing also need to be taken into account.
you can add all you imports and also const definitions to first array parameter like this
const withPlugins = require('next-compose-plugins');
const css = require('#zeit/next-css');
const less = require('#zeit/next-less');
const nextConfig = {
target: 'serverless',
webpack(config, { isServer, webpack }) {
// al your config
return config;
},
};
const redirects = {
async redirects() {
return [
{
source: '/old/blogs/:slug*',
destination: 'whatever your new rewrite url',
permanent: true,
},
];
},
};
module.exports = withPlugins(
[
[css],
[less],
[redirects], // you can directly drop your redirect rules here
],
nextConfig
);
What NextJS Version are you on? Redirects are supported from 9.5 upwards
For anyone who has this problem, try restarting the server. The config file will be reloaded then.
In my case, I tried to redirect to external link. I had trailingSlash: true and I ended my source path with slash.
It didn't work because I use Link component from next/link
I changed it to normal a tag and it worked.
Before:
<Link href="/some-path" passHref>
<a>
to external
</a>
</Link>
After:
{/* eslint-disable-next-line #next/next/no-html-link-for-pages */}
<a href="/some-path">
to external
</a>
You need to disable eslint rule #next/next/no-html-link-for-pages so it won't raise error while building
in next.config.js file:
module.exports = {
trailingSlash: true,
reactStrictMode: true,
async redirects() {
return [
{
source: "/some-path",
destination: "https://example.com",
permanent: true,
},
]
},
}

Localized routes in koa

I'm developing a site with multiple languages. Some routes will therefore also have to be localized and I'm not sure how to do this properly.
I'm using #koa/router for routing.
For this example it's only English and Swedish but the site will handle more languages.
I can setup routes to match words in different languages like
router.get('/(create-account|skapa-konto)/', (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
});
But, I want the English site to only respond to '/sign-up' and send 404 for '/skapa-konto' (and vice versa).
In the real world the route would point to some controller function. So if I set up individual routes for each language I would have to change all localized routes manually should the controller function change in the future. That's something I would like to avoid ;)
Any suggestions?
I ended up solving this by extending the Router like this:
const LocalizedRouter = class extends Router {
/**
* Set up route mapping
* #param {object} options
*/
constructor(options) {
if (!Array.isArray(options.languages)) {
throw new TypeError('Languages must be of type Array');
}
super(options);
this.languages = options.languages;
}
/**
* Router function for GET method
* #param {string | Object<string, string>} RouteCollection
*/
get(routes, func) {
if (typeof(routes) === 'string') {
super.get(routes, func);
return;
}
if (typeof(routes) === 'object') {
for(const key in routes) {
if(!this.languages.includes(key)) {
continue;
}
if(typeof(func) !== 'function') {
throw new TypeError('Middleware must be a function');
}
const checkLanguageAndMount = async (ctx, next) => {
if(ctx.state.lang !== key) {
return next();
}
return func(ctx, next);
};
super.get(routes[key], checkLanguageAndMount);
}
return;
}
throw new TypeError('"Routes" must be a string or an object');
}
};
I can then set up my routes like this:
const myRouter = new LocalizedRouter({
languages: ['en', 'sv']
});
myRouter.get({
'en': '/create-account',
'sv': '/skapa-konto'
}, (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
};
This can probably be cleaned up but it does solve what I wanted to do.
EDIT: Fixed bug that caused 404 if two languages had identical paths
This problem interested me so I created a small github repo with some code. I'll try to explain here:
I created an array with some options:
const localeConfig = [
{
locale: "en",
routes: [
{
path: "/sign-up",
controllers: [enController],
method: "GET",
},
],
prefix: false,
},
{
locale: "se",
routes: [
{
path: "/skapa-konto",
controllers: [seController],
method: "GET",
},
],
prefix: false,
},
];
I then pass this object to a setupRoutes function that basically iterates the array, generating all the routes according to those options.
const setupRoutes = (localeConfig) => {
// Have some check to prevent duplicate routes
localeConfig.forEach((opt) => {
// Adding prefix according to option
const localePrefix = opt.prefix ? `/${opt.locale}` : "";
opt.routes.forEach((route) => {
const path = `${localePrefix}${route.path}`;
router[route.method.toLowerCase()].apply(router, [
path,
...route.controllers,
]);
});
});
};
So, for instance, if you were to change any of the controllers in either language you would only need to update the specific locale object.route.controllers. I imagine you could even have each different locale in a different file to have some modularity.
The github repo is here and I would really like to have you contribute to it if you have any idea on how to improve this.
Cheers!

hapijs - serve static files from directory handler with variable path name

I'm trying to do something like this in my routes file using hapijs + inert plugin
{
method: 'GET',
path: '/l/{path*}',
handler: {
directory: {
path: (req) => {
const defaultDir = '/public';
return getHostInfo(req.headers.host).then((d) => {
if (!d || !d.directory) return defaultDir;
return d.directory;
}).catch((e) => {
return defaultDir;
});
}
}
}
},
path parameter expects a string, array or function which returns a string or array...in my case, my function returns a promise...so it doesn't work.
I tried adding hapi-as-promised package which modifies reply function to support then method but did't work.
Basically I want to serve static assets from one directory or another depending on the host header value.
Well, the only thing i have in my mind is this hack. First, you have to make an extension point to make your async stuff:
server.ext({
type: `onPreHandler`,
method: (request, reply) => {
const defaultDir = '/public';
return getHostInfo(request.headers.host).then((d) => {
if (!d || !d.directory) {
request.app.dirPath = defaultDir;
return;
}
request.app.dirPath = d.directory;
}).catch((e) => {
request.app.dirPath = defaultDir;
}).then(() => {
return reply.continue();
});
}
});
And then the route:
{
method: `get`,
path: `/l/{path*}`,
handler: {
directory: {
path: (request) => {
return request.app.dirPath;
}
}
}
}
I don't know how right is this way, but i tested and it worked. So i hope this helps. And i gotta notice, using node to server files on production isn't a common way by some reason.

Cloud Functions for Firebase: 'Error: could not handle the request'

I feel like pulling my hair out; this is either super simple and i'm having brain freeze or it is not that simple.
What I want
I am trying to unshorten a shortened URL using firebase, when a user goes to:
myapp.firebaseappurl.com/url/SHORTENEDLINK
SO wont let me add a shortened URL
I would like the output to be:
{
"url": "https://stackoverflow.com/questions/45420989/sphinx-search-how-to-use-an-empty-before-match-and-after-match"
}
What I have tried
firebase.json file:
{
"hosting": {
"public": "public",
"rewrites": [ {
"source": "/url/:item",
"destination": "/url/:item"
} ]
}
}
index.js file:
const functions = require('firebase-functions');
exports.url = functions.https.onRequest((requested, response) => {
var uri = requested.url;
request({
uri: uri,
followRedirect: true
},
function(err, httpResponse) {
if (err) {
return console.error(err);
}
response.send(httpResponse.headers.location || uri);
}
);
});
Result
When I go to myapp.firebaseappurl.com/url/SHORTENEDLINK I get the following:
Error: could not handle the request
You are seeing Error: could not handle the request since there probably was an exception and it timed out.
Check your logs using:
firebase functions:log
Refer docs for more details
Here's how I got URL unshortening to work
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const http = require('http');
const urlP = require('url');
const unshorten = (url, cb) => {
const _r = http.request(
Object.assign(
{},
urlP.parse(url),
{
method: 'HEAD',
}
),
function(response) {
cb(null, response.headers.location || url);
}
);
_r.on('error', cb);
_r.end();
};
const resolveShortUrl = (uri, cb) => {
unshorten(uri, (err, longUrl) => {
if (longUrl === uri) {
cb(null, longUrl);
} else {
resolveShortUrl(longUrl, cb);
}
});
};
exports.url = functions.https.onRequest((requested, response) => {
var uri = requested.query.url;
resolveShortUrl(uri, (err, url) => {
if (err) {
// handle err
} else {
response.send({ url });
}
});
});
You can follow the hello world example straight away and use the above code as your function.
Above code uses HEAD requests to peek into 'Location` field of the headers and decides if the url can be further unshortened.
This is lighter as HEAD requests ask for no body (thereby avoiding body parsing). Also, no third party lib required!
Also note that the url passed as a query param. So the request would be
http://<your_firebase_server>/url?url=<short_url>
Saves you the trouble of URL re-writes. Plus semantically makes a little more sense.
Did you tried using { source: '/url/**' } syntax?
You can use something like this;
{
"hosting": {
"public": "public",
"rewrites": [ {
"source": "/url/**",
"function": "/url"
}]
}
}
and then you can parse the url from the request.
exports.url = functions.https.onRequest((req, res) => {
// parse the url from the req and redirect to the correct link
});
You should try this in the firebase.json, its worked for me:
"source": "/**",
I also tried "source": "/url/**" but its not worked.
I think your code is fine. What you're doing incorrectly is that you're using Express-js notations in your firebase.json's rewrites node. (the :item part). These don't work in the Firebase Realtime Database.
So, instead of doing that, change your firebase.json to the following :-
{
"hosting": {
"public": "public",
"rewrites": {
"source": "YOUR SHORTENED URL",
"destination": "YOUR ORIGINAL URL"
}
}
}
This is also the advocated approach in the Cloud Functions for Firebase's URL Shortener sample.
First make sure you are receiving the request properly with the shortened url.
const functions = require('firebase-functions');
const express = require('express');
var express_app = express();
express_app.use(body_parser.text({type: ()=>true}));
express_app.all('*', (req, res) => {
console.log(req.path);
res.send(JSON.stringify(req.path));
});
exports.url = functions.https.onRequest(express_app);
Now when you visit myapp.firebaseappurl.com/url/SHORTENEDLINK you should see the SHORTENEDLINK in plain text. When that's working, try the redirect.
const functions = require('firebase-functions');
const express = require('express');
const request = require('request');
var express_app = express();
express_app.use(body_parser.text({type: ()=>true}));
express_app.all('*', (req, res) => {
var url = req.path;
request({
uri: uri,
followRedirect: true
},
function(err, httpResponse) {
if (err) {
return console.error(err);
}
res.send(httpResponse.headers.location || uri);
}
);
});
exports.url = functions.https.onRequest(express_app);
Also it's good practice to npm install with --save so they end up in the packages.json. While firebase copies your node_modules folder, most other SaaS platforms run npm install.

Resources