Serving create-react-blog in subdirectory - node.js

I need to serve a blog created with create-react-blog in the /blog subdirectory on my main site. It is served alongside other separate projects created w/ create-react-app (the NodeJS code looks like this):
app.use("/blog", express.static(path.join(__dirname, "projects/blog")))
app.get("/blog", (req, res) => {
res.sendFile(path.join(__dirname, "projects/blog/index.html"));
});
app.get("/blog*", (req, res) => {
res.sendFile(path.join(__dirname, "projects/blog/index.html"));
});
app.use(express.static(path.join(__dirname, 'build')));
app.get('/*', function (req,res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
Where the 'build' folder contains the react production build for the main site and 'projects/blog' contains the build files for the blog created with create-react-blog
Navigating to http://localhost:3001/blog with this system does indeed render the blog project, but presumably because Navi was built to used from the head directory the blog returns its 404 page. I can then only view the blog if I use the return link on the 404 page to navigate to http://localhost:3001/ (note: just navigating to http://localhost:3001/ without going through the blog page properly renders the other react project)
I've tried changing the homepage in the package.json of the blog to both /blog and ., changing the basename of the Router in the blog project's index.js to /blog, changing the PUBLIC_URL environment variable to blog and http://localhost:3000/blog, and changing the navi.config.js file to add /blog in front of each item in the getPagePathname function (from
import path from 'path'
export const renderPageToString = require.resolve('./src/renderPageToString')
/**
* Get the file to write each URL to during the build
*/
export function getPagePathname({ url }) {
if (url.pathname === '/rss') {
return 'rss.xml'
}
if (url.pathname === '/') {
return 'index.html'
}
return path.join(url.pathname.slice(1), 'index.html')
}
to
import path from 'path'
export const renderPageToString = require.resolve('./src/renderPageToString')
/**
* Get the file to write each URL to during the build
*/
export function getPagePathname({ url }) {
if (url.pathname === '/blog/rss') {
return 'rss.xml'
}
if (url.pathname === '/blog/') {
return 'index.html'
}
return path.join('/blog/', url.pathname.slice(1), 'index.html')
}
), none of which worked. I've also tried random combinations of the above approaches, which were also unsuccessful.
Please let me know how this would be accomplished. Thanks

After many hours, I was able to move the blog in a fairly roundabout way.
Starting from the template found in the main Github repo mentioned in the first paragraph of the question, I had to do the following combination of things:
Change all '/' routes in routes/index.js to /blog/<route> (i.e. / became /blog, /page became /blog/page etc.
Change the blogRoot in the withContext statement to just state /blog
Change isViewingIndex to req.path === '/blog' || /^\/page\/\d+$/.test(req.path)
In the crawlRoutes function in tags.js, add root='/' to the beginning of the function
Change navi.config.js to
import path from 'path'
export const renderPageToString = require.resolve('./src/renderPageToString')
/**
* Get the file to write each URL to during the build
*/
export function getPagePathname({ url }) {
if (url.pathname === '/blog/rss') {
return 'rss.xml'
}
if (url.pathname === '/blog') {
return 'index.html'
}
return path.join('/blog', url.pathname.slice(1), 'index.html')
}
Change renderPageToString.js to
import renderReactPageToString from 'react-navi/create-react-app'
import renderRSSFeedToString from './renderRSSFeedToString'
/**
* navi-scripts will call this function for each of your site's pages
* to produce its statically rendered HTML.
*/
async function renderPageToString(props) {
if (props.url.pathname === '/blog/rss') {
return await renderRSSFeedToString(props)
}
return renderReactPageToString(props)
}
export default renderPageToString
Of course, you can make this cleaner by changing /blog to be an environment variable (which I highly recommend).
If there is any better solution I am also open to it.

Related

Best way to handle dynamic routes with its own logic in ExpressJS?

I've been tasked with something at work that's beyond my current skills so any help is appreciated.
I'm building an admin where you can add "games". Each game needs to have it's own front-end, routes, and logic.
Kinda like,
mainsite.com/game/game1
mainsite.com/game/game2
mainsite.com/game/game3
At the moment I'm just creating a directory based on the game name.
var dir = "./games/" + req.body.gameId;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
In turn I can pull the .ejs file via:
/* GET dynamic game page. */
router.get("/game/:game", function(req, res, next) {
res.render("../games/"+req.params.game+"/index", { title: "Express" });
});
But I am confused on how it can have it's own logic, routes, connecting to database, front-end, stylesheets inside it's own folder.
There must be a better way to achieve this right?
Cheers
Yes! In Express, you can call app.use() inside a route. You will be able to define a public folder to contain the CSS, JS, and assets that are specific to each route. Just call app.use(express.static('route/to/assets')) inside the route.
app.get('/game/:game', (req, res) => {
app.use(express.static(req.params.game + '/public'))
res.render('../games/' + req.params.game + "/index", { title: "Express" })
})
Seems strange, but perfectly allowed.

react express Uncaught SyntaxError: Unexpected token <

I understand this error is not the reason for the failing. It is failing because in my index.html file i have:
<body><noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="text/javascript" src="/static/js/main.f4a49fba.js"></script>
</body>
That script tag src is failing and returning the same file contents as the index.html itself. This causes it to render HTML (hence < unexcpected from <!DOCTYPE html>).
I am trying to have both express server with /graphql and react together. The working solution is using express.static middlewear shown below. Problem is the working solution breaks the /graphql endpoint so I cannot return any data.
app.use(express.static(path.join(__dirname, 'client/build')));
I need to get this working so it first allows the previous enpoints (/graphql) before checking static pages so I am trying to use this:
app.get('*', (req, res) => {
res.sendFile('index.html', { root: path.join(__dirname, 'client/build/') });
});
This is successfully getting pulled back but failing to work because main.f4a49fba.js in the script tag does not want to load. I tried changing it to /client/build/static/js/main.f4a49fba.js but still wont load. This is using a build file for production.
UPDATE:
I replaced my code with below which helped but for some reason even though I have /graphql above this it is still being run when a full address is being run.
app.get('*', (req, res) => {
const link = (req.path == '/' ? 'index.html' : req.path);
const root = path.join(__dirname, 'client/build');
res.sendFile(link, { root: root }, (error) => {
if (error) {
res.sendFile('/', { root: root });
}
});
});
I am getting this now when a graphql request comes in which seems like it is missing the first /graphql and going to my updated function above. It is very strange and sticking the graphql full address at the end of whatever current page I am in.
http://localhost:3000/dashboard/accounts/my.herokuapp.com/graphql 404 (Not Found)

Serve a file in a browser from a runtime directory using nodejs

In our application we store our reports in a user defined folders. User can add their own folders during runtime. Iam showing the history of those files in a web page. on clicking the file name i should show the file from the folder. How can i show the files from a non public directory.Since its given during runtime i havent added them as static dir to the express server.
One idea we tried was to use node-static-server and create a file server with the folder and serve the file. for each file we create this. it works fine but i get an error saying "port already in use". is there any better idea to do this? is this the right approach?
You can do this in NodeJS using a express.static:
const FS = require('fs')
const express = require('express')
const bp = require('body-parser')
const app = express()
function fileTest(req, res, next){
if (/\.|\/|\\/.test(req.params.file))
return res.sendStatus(400)
return next();
}
app.get(
'/static/:file',
fileTest,
function(req, res, next){
req.url = req.url.replace('/static','')
next()
},
express.static(
'./static',
{
fallthrough: false
}
)
)
app.post(
'/static/:file',
fileTest,
bp.text(),
function (req, res) {
FS.writeFile(
'./static/'+req.params.file,
req.body,
function (err) {
if(err)
return res.sendStatus(500)
return res.sendStatus(200)
}
)
}
)
app.listen(
1337
)
This is a simple example showing a server that will:
[POST]
Take a text body and load it into memory( pitfall: large bodies in memory )
Based on the URL, save it as a file in the static folder
[GET]
Search for a file
If found return file
The good news is that you can make the file and then request the file without restarting the server. Bad news is that this is a VERY SLOW server( comparatively to other options ).
As with all examples no good practices were followed, so be sure to adapt it to your needs.
Things to think about as you adopt it:
How do I allow people to save files to other folders?
How do I disallow people from saving files to other folders I don't want them to?
PROPER AUTHORIZATION

node.js: serve static web, match request url //*/web/ to file system /web/

I use node.js in a simple way to serve a static web.
...
app.use(express.static('./build'));
http.createServer(app).listen(port, ipaddress);
...
This serves the files 1:1 (with index.html as default resource), e.g.
//server/a.html -> ./build/a.html
//server/bbb/x.html -> ./build/bbb/x.html
//server/ccc/ -> ./build/index.html
But now, I need to be able to remove 'one level' of the request url, but it shall serve still the same web, e.g.
//server/aaaa/a.html -> ./build/a.html
//server/bbbb/a.html -> ./build/a.html
//server/xxxx/bbb/x.html -> ./build/bbb/x.html
//server/yyy/ccc/ -> ./build/ccc/index.html
So I need a wildcard matching in the request url. I tried this:
app.use('/\*', express.static('./build'));
http.createServer(app).listen(port, ipaddress);
But with no luck. No more page is accessible. What is wrong?
[Edited to show that the server should serve index.html as default resource]
Depending on your application, you might put express.static() on separate Router instances that are mounted on your app. For example:
var routerA = new express.Router();
// You could also reuse the same static file handler since they
// are all using the same root path
routerA.use(express.static('./build'));
// and other `routerA` route handlers ...
var routerB = new express.Router();
routerB.use(express.static('./build'));
// and other `routerB` route handlers ...
// etc.
However if you don't have your application broken up like this already, you could also specify multiple routes like:
app.use(['aaaa', 'bbbb', 'xxxx'], express.static('./build'));
Or if nothing else, you could just use a custom middleware, calling the static file handler manually (although this is kind of a hack, as it was what separate, mounted Routers were designed to help solve):
var staticHandler = express.static('./build');
app.use(function(req, res, next) {
var m = /^\/[^/]+(\/.+)$/.exec(req.url);
if (m) {
// Temporarily override the `req.url` so that the path
// concatenation will happen correctly
var oldUrl = req.url;
req.url = m[1];
staticHandler(req, res, function(err) {
// Reverting the to the original `req.url` allows
// route handlers to match the request if a file
// was not found
req.url = oldUrl;
next(err);
});
} else
next();
});
app.get('/aaa/foo', function(req, res) {
res.end('hello from /aaa/foo!');
});
My final solution is:
// serve all files from ./web directory regardless of first element in url
app.get('/:leveltoremove/*', function(req, res) {
var path = req.params[0] ? req.params[0] : 'index.html';
res.sendfile(path, {root: './web'});
});
http.createServer(app).listen(port, ipaddress);

Flatiron js - director - how to do async routing from a table?

I'm starting to get things set up using flatiron as the toolset for a web app.
I'm using director with app.plugins.http, and can't seem to figure out how to create a "catchall" route for static files & 404s - It appears that .get("<RegEx>") only matches the first folder position, so if <RegEx> is /.*, it'll match /foo, but not /foo/bar.
Here's my code, as a better example:
in routes.js:
var routes = {
/* home
* This is the main route, hit by queries to "/"
*/
"/" : {
get: function(){
getStatic("html/index.html",_.bind(function(err,content){
if(err) throw err;
renderContent(this,content);
},this));
}
},
/* static files
* Last rule, if no other routes are hit, it's either a static resource
* or a 404. Check for the file then return 404 if it doesn't exist.
*/
'/(.*)' : {
get : function(){
getStatic(this.req.url,_.bind(function(err,content){
if(!err){
renderContent(this,content);
} else {
this.res.writeHead(404);
// TODO: fancier 404 page (blank currently)
this.res.end();
}
},this))
}
}
}
and in my main app file:
/* Define the routes this app will respond to. */
var routes = require('./lib/routes');
/* set up app to use the flatiron http plugin */
app.use(flatiron.plugins.http);
/* loop through routes and add ad-hoc routes for each one */
for(var r in routes){
var route = routes[r];
if(!routes.hasOwnProperty(r)) continue;
for(var method in route){
if(!route.hasOwnProperty(method)) continue;
app.router[method](r,route[method]);
}
}
/* Start the server */
app.listen(8080);
I'd like to be able to keep my routes in a separate module and import them - I'm pretty unclear on if this method or using director and a vanilla http server would be better, but I've tried both ways without any luck.
Here's what I get:
localhost:8080/
>> (content of index file - this works)
localhost:8080/foo
>> (blank page, 404 header)
localhost:8080/foo/bar
>> (no static file for this - I get a 404 header, but the body is now "undefined" - where is this coming from??)
localhost:8080/css/min.css
>> (this file should exist, but the route is never called. I do however still get a 404 header, and get the "undefined" body)
so, I'm assuming the "undefined" body is default behavior for undefined routes.
Is there a way to create a catchall route without adding rules for each depth?
You could try using node-ecstatic which is a static file serving add-on for flatiron. It works well for me and you can find it at:
https://github.com/colinf/node-ecstatic
Try using onError:
app.use(flatiron.plugins.http,{
onError: function (err) {
this.res.end('Nope');
}
});
To manage your static files I suggest you to use flatiron/union + connect.static

Resources