NextJS custom URLs - reactjs

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.

Related

Is it a way to use Next.js and WordPress pages under the same domain?

I have a Next.js site, but for the Landing page I would use a WordPress site. How is it possible?
In the next.config.js, you can set rewrites to work like a proxy with specific application paths. If you want only NextJS homepage router to another domain homepage, you can do this:
module.exports = {
rewrites() {
return [
{
source: "/",
destination: "http://mywordpresssite.com/",
},
];
},
};
Docs
Everything depends on the hosting server that you will use for hosting those website's. You can use one domain for both but you might be forced to use different route's.
Example:
Landing Page : www.example.com or www.example.com/home
Wordpress : www.example.com/news
Using apache for hosting, you can define a .htaccess that would redirect to the right application depending on the route.
I would suggest you to look into that, unless you can provide us more details about your hosting solution.
Edit:
You can check out the vercel configuration file to route something to specific subfolder/files.
More info here: https://vercel.com/docs/configuration#project/redirects

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

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

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

React - Best practice for application with different entry points/sections

I'm working on a larger React Application right now and have some questions about if I set everything up correct (Best Practice). The application has two different "sections". I'm trying to give an easier example (school scenario) how the structure is set up:
The first section of the application is used by principal to set up school classes, teachers, students, rooms, etc...
The section section is used by all the users (teachers, students) that the principal defined. The teachers can set up timetables, etc...
Now what I trying to is: The principal can access his section of the application with the url: admin.school.com - The teachers and students can access the application with the url: school.com
I've put everything in a single react application right now, so I can use the same design, components, etc. During development I switch between the two applications by comment out the application I don't want to access in the index.js file:
// ReactDOM.render(<AppAdmin />, document.getElementById('root'));
ReactDOM.render(<App />, document.getElementById('root'));
My question now is: (How) is it possible that I can get a Build of the application with two "index.html" files so "admin.school.com" points to "admin.html" and "school.com" points to "index.html". My main goal is (like I said) - to have one code basis on my server and just two different html files. Is this possible or do I need to build the application twice? The principal should be able to access the normal section without new login.
I hope that my explanation was easy to understand. Thank you for your help!
You can use process.env to pass environment variables (see https://create-react-app.dev/docs/adding-custom-environment-variables/ if using create-react-app), e.g. in index.js:
const Component = process.env.REACT_APP_ADMIN ? AppAdmin : App
ReactDOM.render(<Component />, ...)
and in package.json:
"scripts": {
...
"build-admin": "REACT_APP_ADMIN=1 npm run build"
}
You can split React application into several Single Page Applications (SPAs). Crisp React boilerplate supports this functionality. Although it uses TypeScript.
My question now is: (How) is it possible that I can get a Build of the application with two "index.html" files.
The two .html files can be called differently.
You can have one SPA called admin with the entry point (also called landing page) admin.html. And another SPA called school with entry point school.html. All you need for that is to modify the SPA Configuration block:
var SPAs = [
new SPA({
name: "admin",
entryPoint: "./src/entrypoints/first.tsx",
redirect: false
}),
new SPA({
name: "school",
entryPoint: "./src/entrypoints/second.tsx",
redirect: true
})
];
SPAs.appTitle = "SchoolApp";
and execute yarn build. Both SPAs are created for you with .html files. Of course you can write admin.tsx, school.tsx and use it instead of first.tsx, second.tsx.
so "admin.school.com" points to "admin.html" and "school.com" points to "index.html".
This is a non-React part.
You create two DNS entries for "admin.school.com" and "school.com", both pointing to the same IP address of your webserver. Let's assume you use NodeJS/Express. In its route handler for / e.g. app.get("/", (req, res, next) => { ... } examine req.hostname and depending on it being either "admin.school.com" or "school.com" make the handler serve admin.html or school.html.

How to redirect crawlers requests to pre-rendered pages when using Amazon S3?

Problem
I have a static SPA site built with Angular and hosted on Amazon S3. I'm trying to make my pre-rendered pages accessible by crawlers, but I can't redirect the crawlers requests since Amazon S3 does not offer a URL Rewrite option and the Redirect rules are limited.
What I have
I've added the following meta-tag to the <head> of my index.html page:
<meta name="fragment" content="!">
Also, my SPA is using pretty URLs (without the hash # sign) with HTML5 push state.
With this setup, when a crawler finds my http://mywebsite.com/about link, it will make a GET request to http://mywebsite.com/about?_escaped_fragment_=. This is a pattern defined by Google and followed by others crawlers.
What I need is to answer this request with a pre-rendered version of the about.html file. I've already done this pre-rendering with Phantom.js, but I can't serve the correct file to crawlers because Amazon S3 do not have a rewrite rule.
In a nginx server, the solution would be to add a rewrite rule like:
location / {
if ($args ~ "_escaped_fragment_=") {
rewrite ^/(.*)$ /snapshots/$1.html break;
}
}
But in Amazon S3, I'm limited by their redirect rules based on KeyPrefixes and HttpErrorCodes. The ?_escaped_fragment_= is not a KeyPrefix, since it appears at the end of the URL, and it gives no HTTP error since Angular will ignore it.
What I've tried
I've started trying using dynamic templates with ngRoute, but later I've realized that I can't solve this with any Angular solution since I'm targeting crawlers that can't execute JavaScript.
With Amazon S3, I have to stick with their redirect rules.
I've managed to get it working with an ugly workaround. If I create a new rule for each page, I'm done:
<RoutingRules>
<!-- each page needs it own rule -->
<RoutingRule>
<Condition>
<KeyPrefixEquals>about?_escaped_fragment_=</KeyPrefixEquals>
</Condition>
<Redirect>
<HostName>mywebsite.com</HostName>
<ReplaceKeyPrefixWith>snapshots/about.html</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
As you can see in this solution, each page will need its own rule. Since Amazon limits to only 50 redirect rules, this is not a viable solution.
Another solution would be to forget about pretty URLs and use hashbangs. With this, my link would be http://mywebsite.com/#!about and crawlers would request this with http://mywebsite.com/?_escaped_fragment_=about. Since the URL will start with ?_escaped_fragment_=, it can be captured with the KeyPrefix and just one redirect rule would be enough. However, I don't want to use ugly URLs.
So, how can I have a static SPA in Amazon S3 and be SEO-friendly?
Short Answer
Amazon S3 (and Amazon CloudFront) does not offer rewrite rules and have only limited redirect options. However, you don't need to redirect or rewrite your URL requests. Just pre-render all HTML files and upload them following your website paths.
Since a user browsing the webpage has JavaScript enabled, Angular will be triggered and will take control over the page which results into a re-rendering of the template. With this, all Angular functionalities will be available for this user.
Regarding the crawler, the pre-rendered page will be enough.
Example
If you have a website named www.myblog.com and a link to another page with the URL www.myblog.com/posts/my-first-post. Probably, your Angular app has the following structure: an index.html file that is in your root directory and is responsible for everything. The page my-first-post is a partial HTML file located in /partials/my-first-post.html.
The solution in this case is to use a pre-rendering tool at deploy time. You can use PhantomJS for this, but you can't use a middleware tool like Prerender because you have a static site hosted in Amazon S3.
You need to use this pre-render tool to create two files: index.html and my-first-post. Note that my-first-post will be an HTML file without the .html extension, but you will need to set its Content-Type to text/html when you upload to Amazon S3.
You will place the index.html file in your root directory and my-first-post inside a folder named posts to match your URL path /posts/my-first-post.
With this approach, the crawler will be able to retrieve your HTML file and the user will be happy to use all Angular functionalities.
Note: this solution requires that all files be referenced using the root path. Relative paths will not work if you visit the link www.myblog.com/posts/my-first-post.
By root path, I mean:
<script src="/js/myfile.js"></script>
The wrong way, using relative paths, would be:
<script src="js/myfile.js"></script>
EDIT:
Below follows a small JavaScript code that I've used to prerender pages using PhantomJS. After installing PhantomJS and testing the script with a single page, add to your build process a script to prerender all pages before deploying your site.
var fs = require('fs');
var webPage = require('webpage');
var page = webPage.create();
// since this tool will run before your production deploy,
// your target URL will be your dev/staging environment (localhost, in this example)
var path = 'pages/my-page';
var url = 'http://localhost/' + path;
page.open(url, function (status) {
if (status != 'success')
throw 'Error trying to prerender ' + url;
var content = page.content;
fs.write(path, content, 'w');
console.log("The file was saved.");
phantom.exit();
});
Note: it looks like Node.js, but it isn't. It must be executed with Phantom executable and not Node.

Resources