Next.js dynamic route - 404 on page reload after added SLUG - node.js

I'm used Next.js and Node.js for my project.
What we have:
Pages structure:
pages/products/PageSomeName.js
pages/products/PageEnotherName.js
pages/products/PageName.js
Pages name in products folder is can be different
routes.js
routes.add("/products/:id", "/products/[id].js");
server.js
app.prepare().then(() => {
const server = express();
server.use(
"./images",
express.static(path.join(__dirname, "images"), {
maxAge: dev ? "0" : "365d"
})
);
server.use(bodyParser.json());
server.get("*", (req, res) => {
return handle(req, res);
});
const PORT = process.env.PORT || 9001;
server.listen(PORT, err => {
if (err) throw err;
console.log(`> Read on http://${process.env.SITE_URL_FULL}`);
});
});
Link component
<Link href={`/products/${data.link}`} as={`/products/${data.slug}`}/>
data array
export const storeProducts = [
{
id: 1,
title: "Title1",
link: "product_name_some",
slug: "product-1-slug",
},
{
id: 2,
title: "Title2",
link: "product_name_different_some_name",
slug: "product-2-slug"
},
There was a problem when I added slug for my links.
By client side everything works fine. I take localhost:3000/product-2-slug in browser url. But after reload, I take 404 error page from Next.js
What I must added by server side in server.js for normal server reloading?
Maybe I need change Link component or next-route settings in routes.js
Thanks!

In server.js you should have a routes as below.
server.get("/products/product1", (req, res) => {
return handle(req, res);
});
server.get("/products/product2", (req, res) => {
return handle(req, res);
});
server.get("/products/product3", (req, res) => {
return handle(req, res);
});
A good way to handle the same is to create a generic products.js inside the pages directory and on server handle as below.
server.js
server.get("/products/:id", (req, res) => {
return handle(req, res, { id: req.params.id });
});
and in
pages/products.js we can create a getInitialProps method
static getInitialProps (context) {
// parameters reqturned from server are accessible via
const id = ctx.query.id
}

Related

React Fetch to Node Server in Herokuapp

I'm attempting to fetch data from my node.js server (which is on port 7000) through react in heroku with this code:
const { name } = e.target.elements;
let details = {
name: name.value,
};
let response = await fetch("/post", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(details),
});
and this server setup:
if(process.env.NODE_ENV === "production") {
app.use(express.static('../client/build'));
app.get('*', (req, res) => {
req.sendFile(path.resolve('client/build/index.html', { root: "../" }));
});
}
app.post("/post", async (req, res) => {
const queryRes = await searchFromQuery(req.body.name);
res.json({
status: queryRes.isFound ? "found" : "notFound", teaminfo: {
name: queryRes.name,
number: queryRes.number,
avgScore: queryRes.avgScore,
predictedScore: queryRes.predictedScore,
}
});
});
app.listen(PORT, console.log(`Server started on port ${PORT}`));
app.get("/", function(req, res) {
res.sendFile('client/build/index.html', { root: "../" });
});
However, when I print out req there doesn't seem to be any data related to the react request. I believe it has to do with my express setup (which I set to express.static() for heroku, though it was initially express.json())
Additionally, the proxy in my package.json is still set to http://localhost:7000.
Issue resolved by adding app.use(express.json()) back to the server file alongside static.

NodeJS Express Routing - Serving Both Frontend and Backend on same NodeJS App

I'm currently trying to wrap my head around the best way to achieve running my frontend and backend on the same NodeJS app.
I currently have two Express routers, one for my frontend and another for the backend.
Below is an extract of my UI/frontend route. On visiting / I want to redirect to /dashboard, I have a middleware that checks if you have accessed dashboard but not authed then you are re-directed to the /login route.
const routes = (app) => {
const ui = Router();
ui.use(function (req, res, next) {
console.log(req.originalUrl)
if (req.originalUrl === '/login' || req.originalUrl === '/register') {
console.log('public route hit')
return next();
}
if (req.isAuthenticated()) {
console.log('user is authed')
return next();
}
console.log('re-directing to login')
return res.redirect('/login');
});
ui.get('/', (req, res) => {
res.redirect('/dashboard'); //If root is navigated to go to dashboard
});
ui.get('/dashboard', async (req, res) => {
res.render('static/dashboard.html', {
locals: {
...globalLocals,
pageName: 'Dashboard'
},
partials: {
...globalPartials
}
});
});
app.use('/', ui);
};
An extract of my API routing is:
const routes = (app) => {
const api = Router();
api.get('/logout', async (req, res, next) => {
await req.logout();
return handleResponse(res, 200, 'success');
});
app.use('/api/v1/', api);
};
The main issue I am having is that by having my frontend router path using / it then runs my authentication middleware on any API calls to /api/v1/***, which results in any login attempts on /api/v1/login result in a re-direction to /login.
How do I get around this issue? Should I have my frontend/UI routing on it's own path, such as /app/ giving me /app/dashboard?

React state hooks work on development but not on build (production)

I developed a bug tracker app using MERN, and on local, the interface updates in real time when the user performs CRUD operations using react hooks (state) and react context.
But once I deployed my app on google cloud, it seemed to turn into a static website, and if I add,edit, or delete, I have to refresh the page to see the change.
What could be causing this to happen?
Here is my server.js code:
require("dotenv").config();
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const cors = require("cors");
const mongoose = require("mongoose");
const ticketRoutes = express.Router();
const PORT = 8080;
var path = require("path");
let Ticket = require("./ticket.model");
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
mongoose.connect(process.env.MONGO_DB, { useNewUrlParser: true, useUnifiedTopology: true }, (error) => {
console.log(error);
});
const connection = mongoose.connection;
connection.once("open", function () {
console.log("MongoDB database connection established successfully");
});
ticketRoutes.route("/").get(function (req, res) {
Ticket.find(function (err, tickets) {
if (err) {
console.log(err);
} else {
res.json(tickets);
}
});
});
ticketRoutes.route("/:id").get(function (req, res) {
let id = req.params.id;
Ticket.findById(id, function (err, ticket) {
res.json(ticket);
});
});
ticketRoutes.route("/add").post(function (req, res) {
let ticket = new Ticket(req.body);
ticket
.save()
.then((ticket) => {
res.status(200).json({ ticket });
})
.catch((err) => {
res.status(400).send("adding new ticket failed");
});
});
ticketRoutes.route("/delete/:id").delete(function (req, res) {
Ticket.findByIdAndRemove(req.params.id, function (err, ticket) {
if (err) {
console.log(err);
return res.status(500).send({ ticket });
}
return res.status(200).send({ ticket });
});
});
ticketRoutes.route("/update/:id").post(function (req, res) {
Ticket.findById(req.params.id, function (err, ticket) {
if (!ticket) res.status(404).send("data is not found");
else ticket.ticket_name = req.body.ticket_name;
ticket.ticket_status = req.body.ticket_status;
ticket
.save()
.then((ticket) => {
res.json({ ticket });
})
.catch((err) => {
res.status(400).send("Update not possible");
});
});
});
app.use("/tickets", ticketRoutes);
app.use(express.static(path.join(__dirname, "frontend/build")));
app.get("/*", (req, res) => {
res.sendFile(path.join(__dirname, "frontend/build/index.html"));
});
app.listen(process.env.port || 8080, () => {
console.log("Express app is running on port 8080");
});
The fix was very in depth, but basically my method of deployment to google cloud was a method that would only work with static web pages. I ended up deploying the app with heroku using this great guide: enter link description here

express.json() doens't work on production

I have a NodeJS and React project. I am having a problem. I didnt understand what the problems are. First I want to show you what my problem is.
on localhost. I want like this on heroku but it returns me as the next one
My problem's photo:
As you see on the photo there is an html file in my user object on Heroku deployment.
But when I start my project on localhost the user has an object that is coming from my mongo database. Express.json() is catching and working correctly on localhost but it doesnt work on Heroku. Why does it happen? What is my problem?
Here is my server.js file :
const express = require("express");
const path = require("path");
const app = express();
const connectDB = require("./config/db");
const PORT = process.env.PORT || 5000;
connectDB();
app.use(express.json());
app.use(express.json({ extended: false }));
app.use(express.static("client/build"));
if (process.env.NODE_ENV === "production") {
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
}
app.use("/", require("./routes/quizRoute"));
app.use("/users", require("./routes/userRoute"));
app.use("/auth", require("./routes/authRoute"));
app.listen(PORT, () => {
console.log("Server is started on the port " + PORT);
});
Client Side request
//SET AUTH WORK
import axios from "axios";
const setAuthToken = token => {
if (token) {
axios.defaults.headers.common["x-auth-token"] = token;
} else {
delete axios.dafaults.header.common["x-auth-token"];
}
};
export default setAuthToken;
//LOAD USER
const loadUser = async () => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/auth");
dispatch({ type: USER_LOADED, payload: res.data });
} catch (err) {
dispatch({
type: AUTH_ERROR,
payload:
"AUTH_ERROR: Token dogrulanamadi veya /auth'a GET isteginde bir sorun var"
});
}
};
enter code here
Why you are having the issue:  
I believe your bundler sets the NODE_ENV value to production when it builds for the deploy environments i.e Heroku. So you are having this issue because of the catch-all route that sends back your client index.html on every get request:
if (process.env.NODE_ENV === "production") {
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
}
Consequently, when you make the get request to the /auth route from your client app, the request gets intercepted by this catch-all route handler before getting to the expected route handler. I'm pretty certain you would get back that HTML string whenever you make any get request to your server, not just the for /auth
The solution:  
The easy fix for this would be for you to move the catch-all route below your API routes like this:
app.use("/", require("./routes/quizRoute"));
app.use("/users", require("./routes/userRoute"));
app.use("/auth", require("./routes/authRoute"));
// Every other API or similar routes should be before this catch-all
if (process.env.NODE_ENV === "production") {
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
}

What are the best practices to work with next.js and express.js and use sessions

for a few weeks now i've started a project using node.js and express.js, I've also added React objects in the front.
I developped a full session manager using express-session and connect-mongodb-session and it works great.
I have one thing left to add now to have everything set up : next.js . And here it gets a little more complicated to me.
My purpose is to have the same thing I have now but with a smooth transition between pages.
I started using the official example from git : https://github.com/zeit/next.js/tree/canary/examples/custom-server-express
Which is very nice. But the pages in /page are public ? Someone could access them by entering their adress, which is not very good because sessions should be completly waterproof.
My first solution is to call them random names so people can't access them directly, but it's not very neat ... And they could still workarround ... This would make me write somehing like this :
server.get('/nice_name', (req, res) => {
console.log('loaded') return app.render(req, res, '/uoiuouiorlos_ugly_random_name', req.query) })
My other solution is to make a route for their adress and call
(req,res,next) => next()
This could work but ... is it pretty ?
Another solution is to edit next.config.js and write :
module.exports = {
useFileSystemPublicRoutes: true,
}
it works great but then every page is loading like they would normally do ... So what would be the point ?
const express = require('express')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
console.log('page loaded')
app.prepare().then(() => {
const server = express()
server.get('/', (req, res) => {
console.log('loaded')
return app.render(req, res, '/1A', req.query)
})
server.get('/space2', (req, res) => {
console.log('loaded')
return app.render(req, res, '/2A', req.query)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
I'm a beginner using next, thank you in advance for your help !
for now redirecting to a page named 404.js if people try to access the actual page works fine
express.get('/', (req, res) => {
return app.render(req, res, '/landing_page', req.query);
})
express.get('/landing_page', (req, res) => {
return app.render(req, res, '/404', req.query)
});

Resources