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

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,
},
]
},
}

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?

Masking a URL destination file using Rewrite in Multi language config

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,
},
};

Image uploaded works in localhost but not on server (s3)

Well, I have an option in the system where I can see some documents sent by the user. But the images appear as broken icons.
As "localhost" everything works great, but I can't see the images when I run straight from the website. (On the s3 everything is working normally, I can see the images.)
In the brownser console, an error is thrown about the route that the request is being made "api.mysite.com/the-image-example.png" with status code 404.
What I don't understand is why the request is being made there and not directly on amazon s3 (there I have the images loading normally). Someone cal help me with that??
How images appear:
This is the code
Upload config:
const tmpFolder = path.resolve(__dirname, '..', '..', 'tmp');
export const mimeTypesPhotos = [
'image/png',
'image/jpeg',
'image/bmp',
'image/webp',
];
export const mimeTypesVideos = ['video/webm', 'video/ogg', 'video/mp4'];
export const mimeTypesPng = ['application/pdf'];
const pngRoutes = ['/requests/test', '/test/:id/confirm'];
interface IUploadConfig {
driver: 's3' | 'disk';
tmpFolder: string;
uploadsFolder: string;
multer: multer.Options;
image: {
height: number;
width: number;
};
config: {
disk: unknown;
aws: {
bucket: string;
};
};
}
export default {
driver: process.env.STORAGE_DRIVER,
tmpFolder,
uploadsFolder: path.resolve(tmpFolder, 'uploads'),
multer: {
storage: multer.diskStorage({
destination: tmpFolder,
filename: (request, file, callback) => {
const fileHash = crypto.randomBytes(10).toString('hex');
const fileName = `${fileHash}-${file.originalname}`;
return callback(null, fileName);
},
}),
fileFilter: (request, file, callback) => {
const url = matchId(request.originalUrl);
const mimetypes = [...mimeTypesPhotos];
if (pngRoutes.includes(url)) {
mimetypes.push(...mimeTypesPng);
}
if (!mimetypes.includes(file.mimetype)) {
return callback(new Error("File doesn't supported"));
}
return callback(null, true);
},
},
image: {
width: Number(process.env.MAX_IMAGE_SIZE || '1024'),
height: Number(process.env.MAX_IMAGE_SIZE || '1024'),
},
config: {
disk: {},
aws: {
bucket: process.env.AWS_BUCKET || 'mybucket',
},
},
} as IUploadConfig;
Router file (where is calling the route i mean):
app.use('/files', express.static(uploadConfig.uploadsFolder));
The Media Entity:
#Column({ type: 'varchar' })
type: 'photo';
#Column()
media: string;
#Expose({ name: 'mediaUrl' })
getMediaUrl(): string | null {
if (!this.media) {
return null;
}
switch (uploadConfig.driver) {
case 'disk':
return `${process.env.APP_API_URL}/files/${this.media}`;
case 's3':
return `https://${uploadConfig.config.aws.bucket}.s3.amazonaws.com/${this.media}`;
default:
return null;
}
}
Step-1
we need to Enable CORS in the API gateway, so click on the resource and then click on the Action button and Enable CORS. In CORS settings the value of Access-Control-Allow-Headers is '' and Access-Control-Allow-Origin is '' , and leave other settings as it is. Click on Enable CORS and replace existing CORS headers.
Step-2
Now, go to the settings of that API on which you are working. In settings enable Binary Media Types. Set the value of that field like this / , and Save the changes.

SVG sprite issue with Laravel Mix

I'm struggled with the following and would appreciate any help...!
I try to use Laravel Mix (v5.0.4) and extend it with SVG sprite loader (svg-sprite-loader) to generate SVG sprite. I have the following folder structure:
resources/
images/
image.jpg
sass/
app.scss
svg/
file1.svg
file2.svg
webpack.sprite.js
webpack.mix.js
The content of webpack.mix.js:
const mix = require('laravel-mix');
require('./webpack.sprite');
const toCss = 'public/css';
mix.sass('resources/sass/app.scss', toCss)
.options({
sassOptions: {
outputStyle: 'nested',
}
})
.sprite();
The content of webpack.sprite.js:
const mix = require('laravel-mix');
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
const path = require('path');
class Sprite {
dependencies() {
return ['svg-sprite-loader'];
}
webpackPlugins() {
return new SpriteLoaderPlugin({plainSprite: true});
}
webpackRules() {
return {
test: /\.svg$/,
include: path.resolve(__dirname, 'resources', 'svg'),
use: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
spriteFilename: path.resolve(__dirname, 'resources', 'images') + 'sprite.svg',
runtimeCompat: true
}
},
'svg-transform-loader',
'svgo-loader'
]
};
}
}
mix.extend('sprite', new Sprite());
It does NOTHING in regards sprite, but it generates the CSS from SASS! :( I don't know why... Tried to "debug" it with some console.log() in the extension and it was hit, I saw the log messages in the console. But the sprite wasn't generated.
I also tried to use just hardcoded, relative paths in the extension without path. Didn't help.
Any idea?
Thanks in advance!
I have a feeling this is related to Webpack5's new Asset module.
https://webpack.js.org/guides/asset-modules/
For assets to be written to disk, or possibly primed to be handed off to large plugins you need to now specify asset type and generator to best define a filename for these assets.
webpackRules() {
return {
test: /\.svg$/,
include: path.resolve(__dirname, 'resources', 'svg'),
type: 'asset/resource',
generator: {
'filename': '[name][ext]'
},
use: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
spriteFilename: path.resolve(__dirname, 'resources', 'images') + 'sprite.svg',
runtimeCompat: true
}
},
'svg-transform-loader',
'svgo-loader'
]
};
}
If still no luck try an alternative plugin:
https://www.npmjs.com/package/webpack-svg-spritely

Webpack externals in both node and the browser

I have an isomorphic React application which runs in both the browser and on the server. I build the same code for both by running two separate Webpack builds through two different entry points and with different configs.
The problem is that the external file that exists on the browser window via an external script tag (Google Maps in this instance) obviously won't exist when running in node on the server. The code is identical except the entry point file.
index.html:
// index.html
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=XXX"></script>
Simplified config:
// Server Webpack config
{
entry: 'server.js',
target: 'node',
externals: {
google: google
}
}
// Client Webpack config
{
entry: 'client.js',
target: 'browser',
externals: {
google: google
}
}
The Component:
// The view which builds and runs fine in
// the client but doesn't run on the server.
var React = require('react'),
css = require('./style.css'),
google = require('google'); // Nope, not on the server obviously!
var Component = React.createClass({
render: function () {
return (
<div>
// Do maps stuff
</div>
);
}
});
module.exports = Component;
My question is how should I handle this?
Error: Cannot find module 'google'
I currently have a solution which I'm not at all keen on.
// Server Webpack config
{
entry: 'server.js',
target: 'node',
externals: {
google: google
},
plugins: [
new webpack.DefinePlugin({ 'ENV.browser': false }),
]
}
// Client Webpack config
{
entry: 'client.js',
target: 'browser',
externals: {
google: google
},
plugins: [
new webpack.DefinePlugin({ 'ENV.browser': true }),
]
}
// The component
var React = require('react'),
css = require('./style.css');
if (ENV.browser) {
var google = require('google');
}
var Component = React.createClass({
render: function () {
return (
<div>
if (ENV.browser) {
// Do maps stuff
}
</div>
);
}
});
module.exports = Component;
You can use NormalModuleReplacementPlugin to replace the module with a noop, as per an idea from Dustan Kasten:
{
plugins: [
new webpack.NormalModuleReplacementPlugin(/^google$/, 'node-noop'),
],
}

Resources