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
Related
I Have 3 Questions URL Related and looking for an answer or source / topics to learn it from:
How to remove the .html extension from the URL
example: https://stackoverflow.com/questions instead of https://stackoverflow.com/questions.html
How to have Nested Pages in the URL
example: qyz.com/movies/latest/...
how do i configure these variables in a url? (-, ?v='id', &)
example: on a movie app where the user can filter the movies the the filters gets stacked and shows the matching results qyz.com/movies/2022-english-action
Note: I'm only familiar with HTML & Vanilla JavaScript
What you are trying to implement is a Single Page Application (SPA). There is not a correct answer to your question, but multiple frameworks that I could suggest to answer your question - I will tell one example first, and give you a hint of how they work (could differ from one to the other).
One of the simplest that I know is Barba.js, where you put your HTML content in multiple content tags, configure these as pages and their routes, and configure transitions.
Below is an example copied from their documentation (link to this exact documentation by clicking the above hyperlink):
<body data-barba="wrapper">
<!-- put here content that will not change
between your pages, like <header> or <nav> -->
<main data-barba="container" data-barba-namespace="home">
<!-- put here the content you wish to change
between your pages, like your main content <h1> or <p> -->
</main>
<!-- put here content that will not change
between your pages, like <footer> -->
</body>
and the router definition in your main-script.js that you import when executing HTML:
import barba from '#barba/core';
import barbaRouter from '#barba/router';
// define your routes
const myRoutes = [{
path: '/index',
name: 'home'
}, {
path: '/product/:id',
name: 'item'
}];
// tell Barba to use the router with your routes
barba.use(barbaRouter, {
routes: myRoutes
});
// init Barba
barba.init();
Now back to your question topics, which are important configurations required for any SPA:
-> All requests must be proxied to this /index.html file - if you request for /any/other/path, it should actually serve the /index.html file - see below basic NGINX configuration for reference:
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / { # Anything that goes beyond path /*
try_files $uri $uri/ /index.html; # it tries to open files, such as image resources, .js, etc. - if not found, it serves the /index.html
}
}
and 3. -> This is what frameworks try to simplify. They use methods to explode URL and get query parameters from URL - abstracting all the rules to display/hide content based on the URL you are. Below some snippets of these functions mentioned above.
Explode URL (source):
var url = 'http://www.mymainsite.com/somepath/path2/path3/path4';
var pathname = new URL(url).pathname;
console.log(pathname);
Parse query-params (source):
const queryString = "?product=shirt&color=blue&newuser&size=m"; // This can be fetched from "window.location.search" in real world.
const urlParams = new URLSearchParams(queryString);
const product = urlParams.get('product')
console.log(product);
const color = urlParams.get('color')
console.log(color);
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}`)
}
}
Next.js lays out a pretty comprehensive way to get imagery from the /public/ folder (where the app has you store static assets). The pattern is to use fs from Node and do the fetch in getStaticProps.
My attempt:
export async function getStaticProps({ params, preview = false, previewData }) {
const cityData = dataFiltered.find( city => city.citySlug === params.slug )
const cityMapImagePath = path.join(process.cwd(), `/public/static-maps/${cityData.imgPath}`)
const cityMapImageRes = await fs.readFile(cityMapImagePath)
const cityMapImageProcessed = JSON.stringify(cityMapImageRes)
return {
props: {
preview,
cityData: cityData,
cityMapImage: cityMapImageProcessed
},
revalidate: 60,
};
}
This code works, but it returns a pretty weird response when I reference that object in my component:
<img src="{ "type":"Buffer", "data":[255,216,255,224,0,6,75,56,86,87,...] } />
My error has something to do with how I'm processing what fs gives me back. Do I need to encode my jpeg into base64 to get Next to use it? This answer suggests stringifying and then parsing (didn't work for me). Or maybe I need a full blown endpoint to do this? Next isn't very clear on how to get imagery from getStaticProps into the component above it - perhaps you know how?
All data that is returned from the getStaticProps needs to be JSON serializable, so yes, if you want to return image there , you need to base64 encode it (this can be a problem for big images).
The other solution (if the scenario permits it) is not to do it with getStaticProps rather load the image on demand in the front end, by hitting the API after the page has already loaded.
What I ended up doing for the fetch in getStaticProps:
export async function getStaticProps({ params, preview = false, previewData }) {
const cityData = dataFiltered.find( city => city.citySlug === params.slug )
const cityMapImagePath = path.join(process.cwd(), `/public/static-maps/${cityData.imgPath}`)
let cityMapImageRes, cityMapImageProcessed
try {
cityMapImageRes = await fs.readFile(cityMapImagePath)
cityMapImageProcessed = Buffer.from(cityMapImageRes).toString('base64')
} catch {
cityMapImageProcessed = null
}
return {
props: {
preview,
cityData: cityData,
cityMapImage: cityMapImageProcessed
},
revalidate: 60,
};
}
Also make sure up in the component, you are properly encoding your image source as base64 with the data:image/png;base64, prefix. This was a silly mistake that cost me an hour of debugging:
<img src={`data:image/png;base64,${cityMapImage}`} alt='Alt text here' />
Finally, also note that Next.js when used with Vercel will impose a hard, 50MB cap (compressed) on the serverless function used to process your page file request. If you're doing a [slug].js template, all the assets for EVERY page generated will count towards that cap. You'll hit it pretty fast on deploy, so double check yourself.
I'm having some trouble using Filepond for React. Currently, I want to preload a user's profile picture when the page is loaded. I have tried two different approaches but none have worked yet:
First, I try to use the server load option of Filepond like so:
<FilePond
ref={pond}
... other props here
files={[
{
source: this.props.user.id,
options: {
type: 'local'
}
}
]}
server={{
load: (userId, load) => {
fetch(fetchDomain + "/profiles/" + userId)
.then(res => res.blob())
.then(blob => {
console.log('blob is: ', blob)
return load
})
}
}}
/>
I fetch from my express backend which is running on the same domain and returns an s3 image URL. This leads to an infinite loading situation and a picture is never loaded. I kept making small changes to this trying to fix it, but was never able to get it to work. At some point when I was messing with the code it would finish loading, but the image would be gray/unavailable with no error messages.
Another approach I took was simply setting Files to an image URL and this didn't work either. Same result, a greyed out image that I can clear it by clicking the remove item button. I did notice, however, that if that URL was a base64 url it would actually load the image properly.
I've been debugging this for a few hours already. Is there something I am missing here?
I'm not sure if it's relevant, but the setup at the top of my file looks like:
import 'filepond/dist/filepond.min.css';
import CropperEditor from './Cropper';
import FilePondPluginFileValidateType from "filepond-plugin-file-validate-type";
import FilePondPluginImageExifOrientation from "filepond-plugin-image-exif-orientation";
import FilePondPluginImagePreview from "filepond-plugin-image-preview";
import FilePondPluginImageCrop from "filepond-plugin-image-crop";
import FilePondPluginImageResize from "filepond-plugin-image-resize";
import FilePondPluginImageTransform from "filepond-plugin-image-transform";
import FilePondPluginImageEdit from "filepond-plugin-image-edit";
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
const fetchDomain = process.env.NODE_ENV === 'production' ? process.env.REACT_APP_FETCH_DOMAIN_PROD : process.env.REACT_APP_FETCH_DOMAIN_DEV;
// Register the plugins for filepond
registerPlugin(
FilePondPluginFileValidateType,
FilePondPluginImageExifOrientation,
FilePondPluginImagePreview,
FilePondPluginImageCrop,
FilePondPluginImageResize,
FilePondPluginImageTransform,
FilePondPluginImageEdit,
);
const pond = React.createRef();
Here is a picture of what the component looks like when loading after I got rid of all the plugins apart from FilePondPluginImagePreview and use the first method:
It says undefined...
After further reading of the documentation I'm now confused about whether I should be loading or fetching.
I was understanding the load and fetch features of the server object incorrectly. I had to get the image URL before and then use fetch or load within the server object in order to get the file from that URL and then convert it to a blob format. This would also work with an external server endpoint being called within the load feature of the server object as I was trying to do initially, but you'd have to make sure the server returns a file, not a URL. Well, I guess it could return a URL but you'd then have to do another fetch on that URL and then convert it to blob.
The answer to this question is what I did essentially.
File Preview in React FilePond not showing up when setting initial file
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