How to host multiple React SPA apps on a single ASP.NET Core site? - reactjs

I'm trying to run multiple React SPA apps using ASP.NET Core 3.1 with the lastest SpaServices extension and have a problem serving static assets. Much of what I've built comes from a similar question at: How to configure ASP.net Core server routing for multiple SPAs hosted with SpaServices but that was related to Angular and an older version of SpaServices.
My code has two React apps, ClientApp and AdminApp and I have configured each using the following startup code:
app.Map("/client", clientApp =>
{
clientApp.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
});
app.Map("/admin", adminApp =>
{
adminApp.UseSpa(spa =>
{
spa.Options.SourcePath = "AdminApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
});
Going to the /client and /admin routes serves the correct React index.html file, but the linked bundled .js files do not load. Here's an example of the final index HTML for the ClientApp SPA:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="/client" />
<title>Client App</title>
</head>
<body>
<h1>Client App</h1>
<div id="root"></div>
<script src="/static/js/bundle.js"></script>
<script src="/static/js/0.chunk.js"></script>
<script src="/static/js/main.chunk.js"></script>
</body>
</html>
The problem is that the links are relative to the root of the site /static/js/bundle.js and need to be relative to the React app's path. The example file paths should be /client/static/js/bundle.js and /admin/static/js/bundle.js, respectively.
How do I get the system that writes the paths into the index.html file to use the correct root path for the static files? Thanks.

Come up with a possible solution. The expectation is that the build SPA will have the contents of its build directory copied into a folder in the wwwroot folder of the .NET Core project that uses the same name as the route the SPA will use. In this case we have two apps, guestuser and payinguser. The mapping in the Configure function will redirect the user request to pull the static files out of the appropriate wwwroot folder.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSpaStaticFiles();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"))
});
app.Map("/guestuser", mappedSpa=>
{
mappedSpa.UseSpa(spa =>
{
spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions()
{
FileProvider =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/guestuser"))
};
spa.Options.SourcePath = "wwwroot/guestuser";
});
});
app.Map("/payinguser", mappedSpa=>
{
mappedSpa.UseSpa(spa =>
{
spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions()
{
FileProvider =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/payinguser"))
};
spa.Options.SourcePath = "wwwroot/payinguser";
});
});
}
Within the ReactJS project you will want to update/add the homepage property to the package.json file for each project to also use the same name as the folder the site will be hosted in under the wwwroot folder. This will update the paths of the generated code to use the pathname in their file references.
{
"name": "guest-user",
"homepage": "guestuser",
...
}

Related

How does express serve index.html for React App and how do I modify it?

I'm an express noob here and building a React App with server using express and client using create-react-app.
What I want to do
I want to update the title and meta tag in the index.html.
So browser requests url -> Server gets request and adds the title and tag to the index.html -> return it to the browser.
Listed my code here
...
app.use(bodyParser.json())
app.use(aMiddleware)
app.use("/api/foo", bar)
app.use(express.static('client/build'));
if (process.env.NODE_ENV === 'production') {
const path = require('path');
app.get('/*', (req, res) => {
res.sendFile(path.resolve(__dirname, '../client', 'build', 'index.html'))
})
}
Questions
Code is functioning, but I don't know how to replace the title/tag in the index.html
How do I update/replace index for environment that is not prod?
Fo prod environment, I use path.resolve(__dirname, '../client', 'build', 'index.html'), then where is index.html for dev environment? I see there is an index.html in public folder, is it the one that got rendered for dev environment?
I tried to add this code before app.use(express.static(...))
app.get('/', function(req, res) => {
// maybe replace string in the index.html (though I don't know where is it
// then res.send(...)?
})
but this never got triggered. Why?
Stuck on this for a while, tried many things, any help would be great.
You can use react-helmet for this... Or switch to Nextjs which is server side.
https://www.npmjs.com/package/react-helmet

Deploy CRA to s3 and serve with express

I want to deploy CRA bundles to s3 and serve from cloudfront. But index file should be express view (template, I want to add some data to it). All solutions I've found assume serverless or serve CRA index directly from node.
So, I need index to be template and contain cloudfront URLs to bundles. How can I achieve this?
If you need to create and index.html you could do that without template
app.get('/', function(req, res) {
res.type('text/html');
res.send('<html>...</html>');
});
Or with templates like
Jade
doctype html
html
head
title Jade Page
link(href='https://cloudf../css/main.css', rel='stylesheet')
body
h1 This page is produced by Jade engine
p some paragraph here..
Source : https://www.tutorialsteacher.com/nodejs/jade-template-engine
Pug
html
head
title= title
link(href='https://cloudf../css/main.css' rel='stylesheet')
body
h1= message
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
});
https://scriptverse.academy/tutorials/nodejs-express-pug.html
https://stackoverflow.com/a/38047123/3957754

How do I rewrite all urls to index.html in Heroku?

My Heroku app is using React with React Router. I use Switch to navigate through different components, so the URL changes as well (e.g. /room/4141). However, if I reload the page, it doesn't act like if it was a React app, but instead it searches for the mentioned .html file.
I used this Buildpack: https://github.com/mars/create-react-app-buildpack.git but it seems to do nothing in regards with pages being rewritten to index.html.
Is there a way to prevent this behaviour and rewrite all URLs to index.html?
**EDIT:
I'm not familiar enough with express, but here's how the index.html is served.
const express = require("../../node_modules/express");
const app = express();
const server = require("http").Server(app);
const io = module.exports.io = require('../../node_modules/socket.io/lib')(server)
const path = require("path")
app.use(express.static(path.join(__dirname, '../../build')));
if(process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '../../build')));
console.log("DEBUG HERE", __dirname, path.join(__dirname+'../../build'));
//
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname+'../../build/index.html'));
})
}
//build mode
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname+'../../public/index.html'));
})
That buildpack can be configured via a JSON file:
You can configure different options for your static application by writing a static.json in the root folder of your application.
One of the sample routing configurations looks like it does exactly what you want:
When serving a single page app, it's useful to support wildcard URLs that serves the index.html file, while also continuing to serve JS and CSS files correctly. Route ordering allows you to do both:
{
"routes": {
"/assets/*": "/assets/",
"/**": "index.html"
}
}

Why express returns index.html when app.css is requested?

I serve my Angular app using an express server.
var express = require('express');
var server = express();
server.use(express.static('./app'));
server.all('*', function(req, res) {
res.sendFile('index.html', { root: './app' });
});
server.listen(8000);
Full gulpfile.js is here.
When I navigate to http://localhost:8000, my Angular app redirects to http://localhost:8000/home, and app.css is served properly (I get CSS in the response).
However, if I refresh the page (http://localhost:8000/home), the response for app.css is index.html.
Why is this happening, and how would you fix that?
DEMO HERE
You need to use an absolute path.
<link rel="stylesheet" type="text/css" href="/app.css">
Otherwise the request to express for app.css from /home will be home/app.css not /app.css.

Setting up two different static directories in node.js Express framework

Is it possible? I would like to set up two different directories to serve static files. Let's say /public and /mnt
You can also set the path that static files will be served to the web from by specifying an additional (first) parameter to use() like so:
app.use("/public", express.static(__dirname + "/public"));
app.use("/public2", express.static(__dirname + "/public2"));
That way you get two different directories on the web that mirror your local directories, not one url path that fails over between two local directories.
In other words the URL pattern:
http://your.server.com/public/*
Serves files from the local directory public while:
http://your.server.com/public2/*
Serves files from the local directory public2.
BTW this is also useful if you don't want static to serve the files from the root of your server but rather from a more qualified path.
HTH
You can also "merge" directories into a single visible directory
Directory Structure
/static
/alternate_static
Code
app.use("/static", express.static(__dirname + "/static"));
app.use("/static", express.static(__dirname + "/alternate_static"));
Both static and alternate_static will be served as if they were in the same directory. Watch out for filename clobbers, though.
It's not possible by one middleware injection, but you can inject static middleware multiple times:
app.configure('development', function(){
app.use(express.static(__dirname + '/public1'));
app.use(express.static(__dirname + '/public2'));
});
Explanation
Look at connect/lib/middleware/static.js#143:
path = normalize(join(root, path));
There is options.root is static root, which you define in express.static or connect.static call, and path is request path.
Look more at connect/lib/middleware/static.js#154:
fs.stat(path, function(err, stat){
// ignore ENOENT
if (err) {
if (fn) return fn(err);
return ('ENOENT' == err.code || 'ENAMETOOLONG' == err.code)
? next()
: next(err);
Path checked only once, and if file not found request passed to next middleware.
Update for Connect 2.x
Links to code are inactual for Connect 2.x, but multiple static middleware usage are still posible as before.
I also faced the same issue but I managed it to resolve it, after a long search for this quest.
Step 1:
Serve the static files in the pathnames of /public and /mnt
app.use('/public', express.static(path.join(__dirname, '<path_to_the_folder_you_want_to_serve_public>')));
app.use('/mnt', express.static(path.join(__dirname, '<path_to_the_folder_you_want_to_serve_mnt>')));
Step 2:
My plan was to deploy two Angular client apps in a single NodeJS server.
So I ran the 'ng build' on both Angular client apps.
I placed one of dist folder in '/public' folder and another dist folder in '/mnt'.
Step 3:
Need to modify the index.html by changing the following things to show the public folder content,
<script src="./public/runtime.js" defer></script>
<script src="./public/polyfills.js" defer></script>
<script src="./public/styles.js" defer></script>
<script src="./public/vendor.js" defer></script>
<script src="./public/main.js" defer></script>
Need to modify the index.html by changing the following things to show the mnt folder content,
<script src="./mnt/runtime.js" defer></script>
<script src="./mnt/polyfills.js" defer></script>
<script src="./mnt/styles.js" defer></script>
<script src="./mnt/vendor.js" defer></script>
<script src="./mnt/main.js" defer></script>
Important Note : Change the .js files path based on the static folder serving path.
Step 4:
In one path, you can serve public and on another you can serve mnt.
app.get('/', function(req, res) => {
res.sendFile(path.join(__dirname, '../public/dist/index.html'));
})
app.get('/', function(req, res) => {
res.sendFile(path.join(__dirname, '../mnt/dist/index.html'));
})
Now you are good to go. Run & Test it.
const express = require('express');
const path = require('path');
const pagesPath = path.join(__dirname, '/cheatsheet');
const cssPath = path.join(__dirname, '/stylesheet');
const port = process.env.PORT || 3000;
var app = express();
app.use("/cheatsheet" ,express.static(pagesPath));
app.use("/stylesheet",express.static(cssPath));
app.get('/',(request,response)=>{
response.send('Hello CSS!!!');
});
app.get('/bad',(request,response)=>{
response.send({error: 'Bad Request'});
});
app.listen(port, ()=> {
console.log(`Server is running on Port ${port}` );
console.log(__dirname);
});
// folder structure
/cheatsheet/index.html
/stylesheet/style.css
To use express.static inside custom middleware:
app.use(customMiddleware())
where
const customMiddleware = function() {
return function(req, res, next) {
// do some dynamic code
// or
return express.static(__dirname + "/public")(req, res, next);
}
}
we can dynamically enable static files in nodejs server with respect to perticular route
app.use("/test", (req, res, next) => {
if (req.session.isAuth === undefined) {
let middleware = express.static(path.join(__dirname, "staticPages"));
middleware(req, res, next);
} else {
next();
}
});
use :dir instead of *
eg
this.app.use('/:microsite', express.static(path.resolve(process.cwd(), 'client/')))

Resources