How to server a ".html" at certain URL in a Next.js app? - reactjs

I have an ".html" file that I need to serve in a certain route in my Next.js app, like this ...
/pages/customr-route-name/my-html-file.html
So if I go to my website and type http://example.com/custom-route-name/my-html-file.html I can see it
How can I do that in Next.js?

This one requires an API route and a URL rewrite to get working. And the nice thing is, you'll be able to use this pattern for other things too (like if you want to generate an RSS feed or a sitemap.xml).
NOTE: You will need to be running Next 9.5 for this to work.
0. Move your HTML file (Optional)
Your file doesn't need to be located in the ./pages dir. Let's put it in ./static instead. Just replace your these route-file placeholders with your real filename later: ./static/<route>/<file>.html
1. Create the API route
Next lets you create API routes similar to how you would in an old-style host like Express. Any name is fine as long as it's in ./pages/api. We'll call it ./pages/api/static/<route>/<file>.js
// import
import fs from 'fs';
// vars
const filename = './static/<route>/<file>.html';
// export
export default async function api(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.write(await fs.readFileSync(filename, 'utf-8'));
res.end();
}
2. Add a rewrite to next.config.js
In Next, rewrites work similar to their apache counterparts. They map a url location to a Next ./page.
// export
module.exports = {
rewrites: async () => [
{source: '/<route>/<file>', destination: './pages/api/static/<route>/<file>'},
],
};
3. Run it!
You should be able to test this with a regular next dev. Of course, changes to next.config.js require you to manually reboot the dev server.
If you look at the Next docs, you'll see you can use wildcards in both the API routes and these redirects, which might help with the direction you're going.

Update for NextJS Version 11
In your public folder you can create a folder structure patterned after the URL you want.
For example:
public/custom-path-folder-name/file.html
will be served at:
http://localhost:3000/custom-path-folder-name/file.html
For more information see the nextjs docs for static file serving

Related

NextJS custom URLs

I want to transfer my blog from Jekyll to NextJS and looking how to specify custom URLs.
I followed the official NextJS guide on their website, but the routing part was relatively simple. From docs, I got that URLs are based on folders/files structure, but I want to have a subfolder per site topic in the pages folder but keep URLs flat. Something like that:
pages (top-level folder)
investing (subfolder)
how-to-start-investing.js (https://example.com/how-to-start-investing <- no investing folder in URL)
devops (subfolder)
how-to-upgrade-ubuntu.js ([https://example.com/how-to-upgrade-ubuntu <- no devops folder in URL)
In Jekyll, I used Front Matter to specify a custom URL per page. In NextJS looks like I have to use rewrites, but is there any other option?
Also Link component has an attribute to change the link URL, but this is just to display links with URLs.
In NextJS, routes are defined by the structure of the pages directory. If you don't want that, you're going to have to work around that feature. Some options you have:
Use rewrites to map incoming request from /how-to-start-investing/ to /investing/... — note that unlike redirects, rewrites don't expose the destination path, so the user will stay on /how-to-start-investing/ url.
Create files in the root directory called how you want your routes to be exposed (e.g. how-to-start-investing.js). Inside the file, export another component, for example:
// how-to-start-investing.js
import { HowToStartInvesting } from "../components/investing/HowToStartInvesting";
export default HowToStartInvesting;
If you use this approach, I suggest you put your components somewhere outside the pages directory, so you don't have two urls for the same component.
Finally got it to work, so in order to use custom URLs, I have to combine rewrites with redirects, the latter needed for SEO.
Without redirects, I can just use the canonical tag to tell Google my preferred URL, but technically the same article keeps on being available using two URLs, in my case:
sypalo.com/how-to-start-investing
sypalo.com/investing/how-to-start-investing
So the final next.config.js is:
module.exports = {
async rewrites() {
return [
{
source: '/how-to-start-investing',
destination: '/investing/how-to-start-investing'
}
]
},
async redirects() {
return [
{
source: '/investing/how-to-start-investing',
destination: '/how-to-start-investing',
permanent: true
}
]
}
}
And this is to be repeated for all URLs.

Dynamically updating index.html file in production build of a create react app

My client app is built with Create React App, which comes which lots of conveniences built-in. However it's not possible to do server side rendering out of the box. As a result, once the build command is run, all of the output is effectively static.
I have a use case where I do NOT want to switch to complete SSR setup, but would like to dynamically add some data to index.html file so that it is immediately available in javascript when client first loads file.
I worked out the following solution:
React app runs as a docker container, using the serve lib to serve static build content
A separate node service runs in a different docker container and has access to the build content from the react app via a shared volume
The node service runs a function every few minutes that reads the contents of index.html file using fs, inserts some additional data into a script tag (e.g. window.myData={someKey: 'someValue'}), and writes the updated string to index.html.
Locally using docker-compose, this works great. However, I'm wondering about possible ramifications of this approach, especially cases where an incoming request for the react app will fail because of some kind of file lock on index.html as it's being read / written by the node service.
I don't think this would be an issue, but I had enough doubt to post this question. The last thing I can afford are failed requests in my production app because of some unforeseen issue.
Any advice, suggestions, anecdotes, etc. are appreciated!
I've successfully added SSR for CRA without ejecting. If that's your only use case, the SSR setup will be super simple as you don't have to deal with webpack or babel configs. If you are interested, follow 3 steps below:
Step 1: Add a basic Express server in a new folder at your root/ project folder (same level as src/)
Step 2: For main app routes, read file build/index.html and edit it as you want before sending.
Step 3: Other than those routes, serve CRA build/ static files as suggested by CRA SSR documentation
TL;DR
// root/server/index.js (your build is at root/build)
const express = require("express");
const fs = require("fs");
const path = require("path");
const app = express();
// step 2
const renderContent = (req, res) => {
fs.readFile(
path.resolve(__dirname, "../build/index.html"), "utf8", (err, htmlData) => {
if (err) {
return res.sendStatus(500);
}
/* Do something with htmlData here */
return res.send(htmlData);
});
}
app.use("^/$", renderContent); // step 2
app.use(express.static(path.resolve(__dirname, "../build"))); // step 3
app.use("*", renderContent); // step 2
// step 1
app.listen(process.env.PORT, () => {
console.log(`Listening on port ${process.env.PORT}`);
});
Then, you just need to run: node server/index.js, and this server will serve your Create React App normally, except for the part that you edit your HTML above.
I don't know if this will help, but as a suggestion. I faced a similar usecase. I solved it very simply using HtmlWebpackPlugin.
Inside the webpack configuration. I write something like:
new htmlWebpackPlugin({
template: path.join(rootpath, '', 'index.html'),
inject: false //<-- Notice here
})
I set the "inject" to false and using this flag it does not append the script's/bundle on index.html and at the same time it allowed us to use ejs templating to loop over the file's we want to append on index.html.
Like on index.html I wrote:
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<link rel="stylesheet" type="text/css" href="<%= htmlWebpackPlugin.files.chunks[chunk].css %>">
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>
My suggestion would be create a separate chunk/file(js) of that data you want to append dynamically on index.html and using EJS templating, you can achieve the desired result.
How to Create Chunks: READ HERE
Thanks, Hope it Help.

Removing the need for pathing in cloudFront distribution of S3 bucket requiring .html at the end of the page name, in Next.js project

I have a Next.js, React, Ts project that exists on a S3 bucket as a static site and is distributed via cloudFront.
The problem I'm running into is for me to go a different page I have to append .html at the end of the page name.
So mysite.com/profile will return a <Code>NoSuchKey</Code> error, however mysite.com/profile.html will route me correctly.
Is there some way to remove this necessity?
If this is a next issue i'm using
npx next build
npx next export
To build and export the /out directory which I then upload to my S3 bucket
my next.config.js
module.exports = {
target: "serverless"
}
I had it like this as I was originally making use of serverless for Next but have since moved away from it as I'm largely making use of client-side rendering and don't need any of the features it was providing and I am still in the process of doing a cleanup on the project.
Routing in S3 is done with exact match of the file name. You can remove .html extension to use routing as you like. And set metadata Content-type to text/html, to view it properly in browser

How to load dynamic url as static with Zeit.co for nextjs app?

I have a nextjs app that where I have this on my server.js:
server.get('/recipes/:urlId', (req, res) => {
const actualPage = '/recipe'
const queryParams = { urlId: req.params.urlId }
app.render(req, res, actualPage, queryParams)
})
So basically whenever I try to reach /recipes/{something} it renders the page with queryParams.
This is used later to call an api that will reach for the actual recipe information before displaying it back to the user.
If the user navigate from within the application, it works fine and I can pass the parameters properly and everything works ok.
But if I get the url and paste it directly on the url, I get a 404 instead of the recipe.
Running locally it works fine, but when I deploy it to Zeit I get this issue.
Is there something I need to configure there? Does it use the server.js I have set on my app locally or it uses something else?
The problem is the url makes the server to search for the folder recipe and to search for the folder which you have passed the urlId.
There are two ways you can do this.
1) So you have to tell the server that do not look for the folder just redirect the url for any url to index.html page
Or
2) You can use the hash strategy to make the url have # location path
Check the other solution here Router not working.
Or You can add the htaccess file for the server side changes may not be possible

Sending all routes or paths to angular

In my node js app.js I want that whatever the url is it goes to my angular/html page i.e. begin.html, app.js resides in server folder and begin.html is in client folder of my project. It's like :-
-Project
----Server
---------app.js
----Client
---------begin.html
What should I type in app.js so that all the urls go to begin.html where i am using Angular routing?? I think its something like..
var begin=require('../Client/begin.html);
app.use('*',begin);
If you are going to just have all routes go back to that HTML page, then you could just use a web server like Nginx serve that directory statically.
It looks like you are using Express, but that is just a guess. If you want to make HTTP requests from your Angular side to your Node.js side then you will probably want the default response to return your HTML, but still be able to allow requests through. I would look at using the static method from express to expose a static directory while still allowing you to build other routes (i.e. api routes).
It might look something like this:
// Already created express app above
/*
This will default to using index.html from
your Client directory and serve any other resources
in that directory.
*/
app.use(express.static('../Client'));
// Any other routes
app.listen(3000); // Or whatever your port is
Or you could also implement it using the 404 error handler style and return your default file:
/*
This comes after all of your other
route and middleware declarations and
before the .listen() call
*/
app.use(function(req, res, next) {
res.sendFile(path.join(__dirname + '/../begin.html'));
});

Resources