I am using Express and React to build an isomorphic app. I want to use react for a series url with fixed path like: hostname/admin/xxx, hostname/admin/yyy, hostname/admin/zzz.
In Express the router is:
// in server.js file
app.use('/admin', admin);
// in admin.js router file
router.get('*', (req, res) => {
match() // react-router's match method
}
and in the react-router's routes file it is:
<Route path='/' component={Admin}>
<Route path='child' component={Child} />
</Route>
When I visit hostname/admin, the server rendering can match the routes exactly, but the browser thrown an error: Warning: [react-router] Location "/admin" did not match any routes.
If I change the routes to
<Route path='/admin' component={Admin}>
<Route path='child' component={Child} />
</Route>
the server rendering cannot match any routes.
I think the problem is, for server rendering, the path is '/', but for client it is '/admin'. How can I fix it except using app.use('/', admin) in Express?
My final solution is add '/admin' to the location property in match() method:
match({
routes,
location: '/admin' + req.url
}, (error, redirectLocation, props) => {
});
Related
I have an app with frontend and backend. The user can login and sign up and perform CRUD operations. All of the requests defined in the backend are hitting the api. However, some unmatched requests that the user enters in the search bar like: https://myapp.com/foo/bar/foo/bar/ don't hit the backend (I am trying to redirect all such requests by using app.all() by appending it to the end of all the routes in the app). When I enter an unmatched request in the backend api directly, it returns the correct response. SS attached below:
However entering an unmatched from the frontend does nothing. In the network request tab, this is what I see. The request type is document and initiator is other. Also, the request is not logged at the backend. How to solve this issue?
Routes:
import express from 'express'
import { userSignUp, userLogin } from '../controller/userController.js'
const router = express.Router()
router.post('/login', userLogin)
router.post('/signup', userSignUp)
export default router
import express from 'express'
import authorization from '../middlewares/authorization.js'
import {
createTest,
getAllTest,
getSingleTest,
deleteTest,
updateTest,
} from '../controller/workoutController.js'
const router = express.Router()
// router.use(authorization)
//Get everything
router.post('/getAll', getAllTest)
//Get a single workout
router.get('/:id', getSingleTest)
//Post a new workout
router.post('/', createTest)
//Delete a single workout
router.delete('/:id', deleteTest)
//Update a single workout
router.patch('/:id', updateTest)
export default router
server.js file
import dotenv from 'dotenv'
dotenv.config()
import express from 'express'
import morgan from 'morgan'
import workoutRoutes from './router/workout.js'
import userRoutes from './router/user.js'
import mongoose from 'mongoose'
import cors from 'cors'
import multer from 'multer'
import rateLimit from 'express-rate-limit'
import SlowDown from 'express-slow-down'
const app = express()
const upload = multer()
app.use(upload.array())
//get the response in json
app.use(express.json())
//using morgan to log requests
app.use(morgan('dev'))
//using cors to make fetch requests
app.use(cors())
//routes
app.use('/api', userRoutes)
app.use('/api', workoutRoutes)
app.all('*', (req, res) => {
res.status(400).send({ message: 'Invalid Route' })
})
//connect to DB
mongoose
.connect(process.env.MONG_URI)
.then((data) => {
app.listen(process.env.PORT, () => {
console.log('Listening on Port', process.env.PORT)
})
})
.catch((err) => {
console.log(err)
})
Edit 1: Was checking my console.log and saw this console.warn logged for the routes that are unmatched. My best guess now is that react-router is not letting the request hit the api.
SS attached for reference.
I have found the answer and posting this for future geniuses. This goofy question exists because of my lack of understanding of react-router.
I am using react-router and basically inside the browser window all of the routing is handled by the react-router. So, when you hit a path in the address bar, the react router first checks it's routes if that path exists. If it does, the component that exists at that path is rendered (the request is NOT sent to the backend. Requests only reach the backend when a component at that path renders and uses fetch or any other api to get data). Otherwise, it looks for a handler for that path which you can set up using *. Example:
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/:id" element={<SingleBlog />} />
<Route path="/createBlog" element={<CreateBlog />} />
<Route path="/:id/edit" element={<EditBlogs />} />
<Route path="/signup" element={<SignupForm />} />
<Route path="/login" element={<LoginForm />} />
<Route path="*" element={<Page404 />} />
</Routes>
This path with * will serve every url that the react-router does not recognize(This does not mean you shouldn't have a check in the backend. Imagine a person modifiying the request and sending it to the backend). A request is sent to the backend using fetch or axios or any other api. When a page is rendered, you will probably have set up apis to fetch you data from the backend and if that route doesn't exist in the backend then you need to set-up a handler in the backend to catch it and send a response to the user.
I'm relatively new to Nuxt and I'm trying to implement subdomain routing in my Nuxt app, so that a request to:-
mysubdomain.myapp.com is routed to myapp.com/groups/:slug
mysubdomain.myapp.com/<anything else>/ is routed to myapp.com/groups/:slug/<anything else>
where slug is the subdomain.
From what I can see, there are two possible ways to do this. Either extendRoutes in the nuxt config or by using my own router.js file. But I can't work out how get the subdomain into the route.
I've got something like this in my router.js:-
import Router from 'vue-router'
export function createRouter(ssrContext, createDefaultRouter, routerOptions) {
const options = routerOptions || createDefaultRouter(ssrContext).options
const hostname = ssrContext ? ssrContext.req.headers.host : location.host
return new Router({
...options,
routes: fixRoutes(options.routes, hostname.match(/[^.]*/)[0]),
})
}
function fixRoutes(defaultRoutes, subdomain) {
return (
defaultRoutes
.map((route) => {
// TODO use the subdomain in here to route to /groups/:slug route
return route
})
)
}
It feels like I'm close but I can't figure out this last bit. Is this even possible?!
I'm trying to get routing work using Express and create-react-app.
My goal is to address the user to the homepage of the application when the URL is / and to the login page when the URL matches /login.
In my server.js I have two routes defined:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
While apiRoutes contains all the api routing definitions, mainRoutes is responsible for the main navigation (at least this was the idea):
var express = require("express");
var path = require("path");
let router = express.Router();
router.route("/").get((req, res, next) => {
res.sendFile("index.html", { root: "./client/build/" });
});
router.route("/login").get((req, res, next) => {
res.send("This is the login page");
});
module.exports = router;
Somewhere I read about serving the static asset generated by the building process of create-react-app so I added:
// Priority serve any static files.
app.use(express.static(path.join(__dirname, "client/build")));
// All remaining requests return the React app, so it can handle routing.
app.get("*", function(req, res) {
res.sendFile(path.join(__dirname + "/client/build/index.html"));
});
Adding these lines, I successfully see my index.html but I can't visit both /login and /apisubroutes since it redirect me on the main page (index.html) each time.
It's like I need to serve the static files on my subroute mainRoutes but I don't have an idea on how to do that.
How can I make this work?
app.get('*') would match every single route that you have.
You should do something like this:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use(express.static(path.join(__dirname, "client/build")));
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
// If the app reaches this point, it means that
// the path did not match any of the ones above
app.use(function(req, res, next){
// redirect the user to where we serve the index.html
res.redirect('/');
});
create-react-app I believe handles routing different, you cannot hook up the browser's route to the route you want to serve because you're running a single page application", unless you do universal routing with server and the js bundle
OK, I'm still trying to build react.js app with server side rendering. I having big time dealing with react-router with parameters. I cannot extract routes params from route on server side to make proper query on DB.
Here is my react-router route:
import {Router,Route} from "react-router";
import React from "react";
import App from "../components/app";
import {HomeContainer} from "../components/home";
import {TagContainer} from "../components/tag";
export function createRouter(hist) {
const routes = <Route component={App}>
<Route path="/" component={HomeContainer}/>
<Route path="/tag/:unique_name" name="tag" component={TagContainer}/>
</Route>;
return (
<Router history={hist}>{routes}</Router>
);
}
the route run fine until I add parameter ":unique_name" to the route
<Route path="/tag/:unique_name" name="tag" component={TagContainer}/>
on the server side, I cannot extract unique_name from the route to make query on DB:
Here is the route on server(Using Node.js & Express.js):
const router = express.Router();
router.get("/tag/:unique_name", ServerRenderController.tagRender);
server.use(express.static(path.join(__dirname, '..', '/build')));
server.use(router);
and here is my "ServerRenderController.tagRender":
function tagRender(req, res) {
console.log(req.params.unique_name);
/*
output :
mytag_unique_name -> this is the route params
style.css ->stylesheet - how the hell it become route params?
app.js -> client code - how the hell it become route params?
vendor.js -> vendor scripts - how the hell it become route params?
manifest.js -> manifest file -how the hell it become route params?
*/
match({browserHistory,routes, location:req.url}, (err, redirectLocation, renderProps)=> {
if (redirectLocation) {
//#TODO: response redirect location
console.log('redirect location');
}
if (err) {
//#TODO: response error
console.log(err.stack);
}
if (!renderProps) {
//#TODO: route to 404
console.log("no renderProps");
}
renderPage(renderProps); // return HTML to client with __PRELOADED_STATE__
}
Questions :
What did I do wrong in server code (routing, express static
middleware...).
How do I extract correct route params from route? (I only want to extract "mytag_unique_name" as the only params when I browse to http://localhost/tag/mytag_unique_name)
right now the route params including static files that should be
send as MIMETYPE css/js.
OK. Turn out I made mistake in references to style sheets and scripts file.
In the server render code must refer <link href="/style.css" rel="stylesheet"/>
with slash (/) at the begining of the rel attr.
Note here for anyone have same problem.
My app works fine in webpack development server. Now, I want to deploy it to my production server. So I built bundle.js. I tried serving the file on express server, but I can't access urls other than the root /.
For example, here is my react routes:
var Routes = () => (
<Router history={browserHistory}>
<Route path="/" component={Landing}>
</Route>
<Route path="/app" component={App}>
</Route>
</Router>
)
and express app (I put bundle.js and index.html in ./public):
app.use(express.static('./public'));
app.listen(port, function () {
console.log('Server running on port ' + port);
});
Landing page http://localhost:3000/ works. But the app http://localhost:3000/app doesn't. Instead, I got an error Cannot GET /app.
You need to declare a "catch all" route on your express server that captures all page requests and directs them to the client. First, make sure you're including the path module on your server:
var path = require('path');
Then, put this before app.listen:
app.get('*', function(req, res) {
res.sendFile(path.resolve(__dirname, 'public/index.html'));
});
This assumes you're inserting bundle.js into index.html via a script tag.