Differences between query routes in nextjs using next-routes - javascript

I'm working on a project with next.js and Reactjs that uses a lot of different languages. So I need to change the language url. Example:
www.example.com/es/entradas
www.example.com/en/tickets
www.example.com/de/eintrittskarten
To make routes I saw that there is a module that helps me: next-routes
https://github.com/fridays/next-routes
There are a lot of url and I'm working with a CMS, so my clients will be able to add more, so routes can't be harcoded. I thought to pass the url with queries, like this:
const routes = require('next-routes');
module.exports = routes()
.add('index', '/:lang?')
.add('tickets', '/:lang?/:ticket')
.add('hotel', '/:lang?/:hotel');
My surprise (as you might see), it doesn't work because routes doesn't see the difference between these two last routes. If I write:
www.example.com/en/tickets
It will go correctly to my page "tickets" but if I write
www.example.com/en/hotel
It will go again to my page "tickets" and not to "hotel"
Do you know any way about how could I make this?
In my project I have these files related about routes:
server.js
const next = require('next');
const { createServer } = require('http');
const routes = require('./routes');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dir: './src/shared', dev });
const handle = routes.getRequestHandler(app);
app.prepare()
.then(() => {
createServer(handle)
.listen(3001, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3001");
});
});
routes.js
const routes = require('next-routes');
module.exports = routes()
.add('index', '/:lang?')
.add('tickets', '/:lang?/:ticket')
.add('hotel', '/:lang?/:hotel');

The request for /en/hotel is going to the "ticket" route because it is actually matching it.
This is because of the : in front of the word "ticket" in the route. A : will turn a section of a route into a parameter with that name.
So instead what you probably want is:
module.exports = routes()
.add('index', '/:lang?')
.add('tickets', '/:lang?/ticket')
.add('hotel', '/:lang?/hotel');

Related

Best Practice for Route Architecture

I'm trying to set up routes for my backend. I have two ways that I've tried setting these routes up, and I'm wondering which way fits best practices (or neither?). The differences are minimal, but I'd love to know if there is an objective "best" here.
Here are my attempts:
const express = require("express");
const router = express.Router();
const flashcardController = require('../controllers/flashcardController');
router.get('/', flashcardController.readFlashcard);
router.post('/', flashcardController.createFlashcard);
router.patch('/', flashcardController.updateFlashcard);
router.delete('/', flashcardController.deleteFlashcard);
module.exports = router
VS
const express = require("express");
const router = express.Router();
const flashcardController = require('../controllers/flashcardController');
module.exports = (app) => {
router.get('/api/flashcard', flashcardController.readFlashcard);
router.post('/api/flashcard', flashcardController.createFlashcard);
router.patch('/api/flashcard', flashcardController.updateFlashcard);
router.delete('/api/flashcard', flashcardController.deleteFlashcard);
app.use('/', router);
};
Of course, my app.js (entry-point for my backend) file will need to be coded slightly differently for each of these options.
If you believe that the job of a router is to just handle some requests that it receives and it is the job of the calling code to place the router at whatever path the calling code wants it to operate on, then only your first option will do that. This would allow a caller to use these routes in whatever path it wants.
If you want the module that implements the routes to be entirely self-sufficient and install the routes on the path it wants them to be on, then only the second option does that.
I would say that the "usual" and more "flexible" scheme is the first one where the caller places the routes on the path where it wants them. But, you are free to choose whichever style you want.
The second option is not implemented particularly efficiently so it could be improved. No router is needed at all as the routes can be just installed directly on the app object directly. And, repeating /api/flashcard multiple times can be avoided.
For example, the second option could be this:
const controller = require('../controllers/flashcardController');
const routePath = '/api/flashcard';
module.exports = (app) => {
app.get(routePath, controller.readFlashcard);
app.post(routePath, controller.createFlashcard);
app.patch(routePath, controller.updateFlashcard);
app.delete(routePath, controller.deleteFlashcard);
};
Or, even just this:
const controller = require('../controllers/flashcardController');
module.exports = (app) => {
app.route('/api/flashcard')
.get(controller.readFlashcard)
.post(controller.createFlashcard)
.patch(controller.updateFlashcard)
.delete(controller.deleteFlashcard);
};
And, the first one could be simplified to this:
const router = require("express").Router();
const controller = require('../controllers/flashcardController');
router.route('/')
.get(controller.readFlashcard)
.post(controller.createFlashcard)
.patch(controller.updateFlashcard)
.delete(controller.deleteFlashcard);
module.exports = router

React Redux bundle.js being thrown into request

I have an issue with how my React Redux SSR application is handling site navigation I have a route for list pages which will display different data depending on the params in the URL.
Routes.js file
export default [
{
...App,
routes: [
{
...HomePage,
path: '/',
exact: true
},
{
...ListPage,
path: '/list/:id',
exact: true
},
In my Index.JS file where my express backend is running I iterate through my routes directory to see the path(s) that matches the request path...
const app = express();
app.use(express.static('public'));
app.get('*', (req, res) => {
const store = createStore(req);
const promises = matchRoutes(Routes, req.path)
.map(({ route }) => {
console.log("Looking at Route: ", route);
if (route.loadData) {
const params = req.path.split('/');
console.log('my params are: ', params)
return route.loadData(store, params[2])
}else{
return null
}
})
.map(promise => {
if (promise) {
return new Promise((resolve, reject) => {
promise.then(resolve).catch(resolve);
});
}
});
Promise.all(promises).then(() => {
const context = {params: req.params};
const content = renderer(req, store, context);
if (context.url) {
return res.redirect(301, context.url);
}
if (context.notFound) {
res.status(404);
}
res.send(content);
});
});
My understanding is that there should only be 2 things to iterate over, the App component Route, and the ListPage component Route it then calls their respective loadData() functions and the websites continues to run. However after it goes through the first 2 routes and populates my page with the relevant information the Index.js file gets called again and iterates through the routes but this time instead of having the URL that the user is trying to access it replaces it with "bundle.js" and I don't understand what's going on here. This is the output I get I would love to only have the top half of the output.
NOTE this image is taken from my console (I've combined both the client and server side output in 1 window) below I'll include a screenshot of my config Files
Of course my code wasn't expecting this as a path and the application breaks because it's trying to get information on a list with the ID of "bundle.js" instead of a standard number.
Question can someone explain to me what my codes doing wrong here or if this is how it's supposed to behave how I work around this I'd greatly appreciate it.
I'm currently trying to create my first SSR application so I'm new to this technology so I might be missing something obvious.
Upon further investigation I noticed that the file bundle.js that I could see in the console was referring to a file at location /list/bundle.js but my bundle was actually in my public directory so I had to modify the script Src so that it would refer to the http://localhost:3000/bundle.js after I did this app functioned how It was supposed.

Adding a routing file inside another routing file node.js

I want my routes to be like the following:
/
/business
/business/stuff1
/business/stuff2
/business/admin
for /business I want to have a separate file for the routing and functions.
and also for /business/admin I want to have a separate file for the routing and functions.
so what I did is:
app.js
//Business route
const business = require("./business/business");
app.use("/business", business);
This works fine - but when I add in business.js
//admin route
const admin = require("./admin");
app.use("/admin", admin);
I get 404 for some reason.
Depends on what you are exporing from business.js. It should be an instance of express.Router and you have to mount the /admin route on this instance. Example:
// business.js
const admin = require("./admin");
const businessApp = express.Router();
businessApp.use("/admin", admin);
module.exports = businessApp;

Running Code When a Module is Loaded and Exposing It as Middleware

I am still trying to fully understand how exporting and importing modules works in Nodejs.
I am using the following file to seed a mongodb database. This file runs exactly as it should and returns exactly the result I am expecting, when I execute it as a standalone file. My issue is I want to use this file in two different places in my app. So I am trying to make it an exportable/importable module. Here is what I have tried:
seed.js looks like this:
'use strict';
// library modules
const {ObjectID} = require('mongodb');
const seeder = require('mongoose-seed');
const jwt = require('jsonwebtoken');
const util = require('util');
// local modules
const {Course} = require('./../models/course');
const {Review} = require('./../models/review');
const {User} = require('./../models/user');
const {data} = require('./../data/data.js');
const config = require('./../config/config.js');
/*==================================================
build seeding Courses, Reviews, Users
==================================================*/
// Connect to MongoDB via Mongoose
let seed = seeder.connect(process.env.MONGODB_URI, (e) => {
console.log(`Connected to: ${process.env.MONGODB_URI} and seeding files`);
// Load Mongoose models
seeder.loadModels([
'src/models/user.js',
'src/models/review.js',
'src/models/course.js'
]);
// Clear specified collections
seeder.clearModels(['User', 'Review', 'Course'], function() {
// Callback to populate DB once collections have been cleared
seeder.populateModels(data, function() {
seeder.disconnect();
});
});
});
module.exports = seed;
Within app.js I have these two lines
const seed = require('./middleware/seed');
and
app.use(seed);
I have also tried, to no avail
app.use(seed());
What is missing? I don't want to use the code in-line in two different places (DRY you know).
It is failing with this error:
throw new TypeError('app.use() requires a middleware function')
I am sorry about the formatting I thought I was using markdown, but I am clearly not.
You are executing your seeder function in that module and not returning any middleware function at all. Middleware is always a function and it has the form of:
const seed = function (req, res, next) {
...
next()
}
With what you have now, the best you can do is the following which will certainly seed your models when the module is loaded.
const seed = require('./middleware/seed');
Are you trying to run those two functions as middleware, on every request? In that case you'd do something like this:
const seed = function (req, res, next) {
seeder.connect(..., (e) => {
...
seeder.clearModels(..., ()=>{
...
next()
})
})
})
If you want to run that seeder code at the module level AND to expose it as middleware then wrap the current module level code in function and execute it when the module loads. Notice that the new function optionally handles the next callback if it receives it.
function doSeed(next){
//your code currently running at module level
if(next)
return next()
}
doSeed(); //run the code at the module level
const seed = (req, res, next) => doSeed(next))
module.exports = seed;
One way that I allow myself to have access to a variable in different instances is adding it to the process variable. In node.js no matter where in the project it is, it will always be the same. In some cases, I would do process.DB = seed(); which would allow process.DB to always be the result of that. So inside your main class you can do process.DB = seed(); and all of your other classes running off of that one main class would have access to process.DB keeping your database readily available.

React server side rendering renders incorrect content

I'm working on a react web app using React, Redux, and React Router, with server side rendering (using express)
The problem I'm facing is a bit hard to explain. I will try to explain it in the following steps.
You first enter the app from a URL like http://www.example.com/articles/1234. The express server will send down the correct content which includes the correct page source and the DOM(from chrome Element panel) as the initial load.
Then let's navigate to a different page like http://www.example.com/articles/5678 (navigating is not causing page refresh since it's a single page app, it only reaches out to the server for SSR content when you do a refresh with your browser or entering a page from a URL in browser address bar) everything works fine so far.
Refresh the page in the browser (You're now on page http://www.example.com/articles/5678) with cmd + r or F5. Again, the server will send down the content. But this time the content that you're receiving is a bit different. The page content in the browser is correct as well as DOM from chrome Elements panel. However, the page source is not correct, it's still the old page source that you got from step 1.
I have tried to log out the content when refreshing the page. The content that the server sent down in step 3 is not the correct content (It's the previous page) but somehow the browser can still see the right content.
And if I refresh the page once more after step 3 then I will get both the correct content and correct page source...
I'm also using Facebook open graph debugger. The debugger is telling me that it follows a redirect to step 3. And the redirect URL it followed is the previous page URL. I know this not quite right, but not sure where I'm doing it wrong...
Here are my express server settings
Thanks for the help!
server.js
require('babel-register');
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const ReactRouter = require('react-router');
const ReactRedux = require('react-redux');
const Store = require('./src/store/configureStore').default;
const routes = require('./src/routes').default;
const compression = require('compression');
const morgan = require('morgan');
const store = Store();
const match = ReactRouter.match;
const RouterContext = ReactRouter.RouterContext;
const Provider = ReactRedux.Provider;
const port = process.env.PORT || 5050;
const ReactHelmet = require('react-helmet');
const Helmet = ReactHelmet.Helmet;
const app = express();
app.use('/dist', express.static('./dist'));
app.set('view engine', 'ejs');
app.use(compression());
app.use(morgan('combined'));
app.use((req, res) => {
// prettier-ignore
match(
{ routes, location: req.url },
(error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
console.log(renderProps);
const body = ReactDOMServer.renderToString(
React.createElement(
Provider,
{ store },
React.createElement(RouterContext, renderProps)
)
);
const meta = Helmet.renderStatic().meta.toString();
res.status(200).render('index', {body, meta});
} else {
res.status(404).send('Not found');
}
}
);
});
console.log('listening on port ' + port); // eslint-disable-line
app.listen(port);
I have eventually resolved the issue.
The main problem was that I placed my async data fetching calls in the componentWillMount lifecycle method instead of componentDidMount.
And I didn't properly handle async data fetching in my App.
I'm now using the approach mentioned in this article to deal with my async data fetching issue.
Basically, I'm doing the following:
On the client side: Create a static method fetchData in components.
On the server side: Use React Router to grab the right component and use the fetchData method to fetch the data needed for that component.
After these promises have been resolved, get the current state from Redux store and render the HTML and send it down to the client.
Inject the state to the client side Redux store and render the page.

Categories