Express serves static files that are not in root directory - javascript

I have a website with a lot of HD videos so I want to put the videos files outside of web root directory.
Here is my web root directory:
/var/node/myapp
For some basic static files like javascript, css... I put them in public directory.
/var/node/myapp/public
For video files I want to put here
/hdd/videos
This is my current serve static code:
app.use(serveStatic(path.join(__dirname, 'public'), {
maxAge: keys.conf.maxAge,
etag: true,
setHeaders: setCustomCacheControl
}));
function setCustomCacheControl (res, path) {
if (serveStatic.mime.lookup(path) === 'text/html') {
res.setHeader('Cache-Control', 'public, max-age=0')
}
}

You can set multiple static directories. Example:
app.use(express.static('public', {etag: true, maxAge: keys.conf.maxAge}));
app.use(express.static('/hdd/videos'));
However, the path that you provide to the express.static function is relative to the directory from where you launch your node process. If you run the express app from another directory, it’s safer to use the absolute path of the directory that you want to serve.
Document for express static file in here

If you want to have a directory accessed outside of the root web server you'll need to go up a directory level via ... You didn't specify where exactly /hdd/videos is in relation to your root directory, but it should change to look something like this:
var videosDirectory = __dirname + '/../../hdd/videos';
app.use(serveStatic(videosDirectory, {
maxAge: keys.conf.maxAge,
etag: true,
setHeaders: setCustomCacheControl
}));
function setCustomCacheControl (res, path) {
if (serveStatic.mime.lookup(path) === 'text/html') {
res.setHeader('Cache-Control', 'public, max-age=0')
}
}

You can create a symlink under /var/node/myapp/public to point to /hdd/videos:
On Linux/Unix/OSX, for example:
ln -s /hdd/videos /var/node/myapp/public/videos
This way you don't expose your entire root directory, and you can separate where you store the actual videos from where you serve them without copying or moving them when they're added/removed/etc.
When the user hits your route /videos it will look for it under /var/node/myapp/public as per your static route. It will see videos as a link and follow that link to /hdd/videos where the videos will be available.

Related

Problem with static routing in Node.js using express

I am having an issue with some custom routing code, it all works fine and is in sync with the client-side view routing I do, but as soon as I have a subpage, it doesn't route my static files correctly.
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
Rather than giving me a file from the root directory, it'll serve it as if it were from the subfolder.
Example: i go to http://localhost/sign-up, and files loading in my index file from /scripts are loaded, but if i go to http://localhost/sign-up/2, it'll attempt to load the script from /sign-up/scripts
const express = require('express');
const path = require('path');
const app = express();
app.use('/views', express.static(path.resolve(__dirname, 'frontend', 'views')));
app.use('/styles', express.static(path.resolve(__dirname, 'frontend', 'styles')));
app.use('/scripts', express.static(path.resolve(__dirname, 'frontend', 'scripts')));
app.use('/media', express.static(path.resolve(__dirname, 'frontend', 'media')));
app.get('/*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'frontend', 'newSite.html'));
});
app.listen(process.env.PORT || 1234, () => console.log('Server is now running...'));
To manage this I have been following these tutorials by DCODE on youtube, but I can't see anything amiss:
https://www.youtube.com/watch?v=6BozpmSjk-Y
https://youtu.be/OstALBk-jTc
Resources loaded in the sign up folder should use URLs beginning with a '/' character, to make them relative to the site root, e.g.
src="/scripts/modulefile.js"
href="/css/stylesheet.css"
href="/media/image.png"
and not urls relative to the signup folder - which they will be if the leading '/' is omitted.
You don't need multiple routes to serve your static contents, and the static method of express do such kind of tasks for you:
// If your 'public' or 'static' directory is one of root directories
app.use(express.static(process.cwd() + '/public'));
// so all these requests will be served:
// -> /public/styles/custom.css
// -> /public/scripts/pollyfils.js
// -> /public/media/logo.png

NextJS deploy to a specific URL path

I am working on my first NextJS application. When I run "npm run dev" or "npm run start" it deploys my application to
http://host:port/
When I navigate to a page the url becomes
http://host:port/page1
I need to have my own specific URL, such as
http://host:port/my-test-application/path-for-my-app/
http://host:port/my-test-application/path-for-my-app/page1
Furthermore, my app has a lot of elements to link to other areas of the applications, i need these to also go to URL with the basePath and not just go to the root path.
I will also be depolying this app to different servers which will have different basePaths, therefore this can not be hardcoded in my app.
How can I do this?
With other applications such as react/vue/angular/native JS, I simply build my application and put the build code in a "my-test-application/path-for-my-app" folder on my server.
I tried this with my NextJS application but i got an error that ".next" folder could not be found.
I googled and could find some references to using "assetPrefix" or using "Zones". However I do not really understand what I am supposed to do.
How do i get my app deployed to specific URL
Solution 1: Restructure "pages" - Does not enable me to deploy to different servers with different basePaths
I could create the folder structure inside my "pages" directory and change all my elements to use this folder structure.
|- pages
|- my-test-application
|- path-for-my-app
|- index.js
|- page1.js
<Link href="/my-test-application/path-for-my-app/page1" >
I dislike this solution as the basePath is hardcoded into my application, as to apposed to a deployment setting.
If I wanted to deploy my app on 2 servers with different basePaths (i.e. below) I would have to have 2 versions of the code.
http://host:port/my-test-application_1/path-for-my-app/page1
http://host:port/my-test-application_2/diff-path-for-my-app/page1
Updated: I have updated this question on 5th March to include my need for s to work and one solution which I do not like.
In Next.js ≥ 9.5, you can set a basePath in your next.config.js. For example, if you want your entire Next.js app to live at /docs, you can use:
// next.config.js
module.exports = {
basePath: '/docs'
}
Next will will make sure all assets are served from the right place, and it will automatically prefix this base path for all Links in your app, as well as during programmatic navigation using next/router. For example,
<Link href="/about">
<a>About Page</a>
</Link>
will be transformed to link to /docs/about, as will
router.push('/about')
This means that you can change basePath without changing anything at all in the actual code of your app.
I found a solution using NGINX to reverse proxy a URL with the base path.
Useful links
https://levelup.gitconnected.com/deploy-your-nextjs-application-on-a-different-base-path-i-e-not-root-1c4d210cce8a
https://www.docker.com/blog/tips-for-deploying-nginx-official-image-with-docker/
Application Changes
Dependencies
next-images : in order to import static images from "public" when using a reverse proxy
#zeit/next-css : in order to use stylesheet files
as well as usual NextJS dependencies
next.config.js
Add a "next.config.js" file at the root of your application so that you can specify the "assetPrefix" and "publicRuntimeConfig.basePath"
assetPrefix : used by NextJS when accessing components, stylesheets, pages etc
publicRuntimeConfig.basePath : used in s so specify the prefix to add to the link, used in "src" tags of "" elements when using public images
Example
const isProd = process.env.NODE_ENV === 'production'
// Enable importing of css stylesheets
const withCSS = require("#zeit/next-css");
const withImages = require('next-images');
/*
* Gets the BASE_PATH from the command used to start this app.
* If BASE_PATH is specified but it does not start with a "/"
* then add it.
*/
function getBasePath() {
var basePath = ''
if (isProd && process.env.BASE_PATH){
if (process.env.BASE_PATH.startsWith("/") ){
basePath = process.env.BASE_PATH;
} else {
basePath = "/" + process.env.BASE_PATH;
}
}
console.log("getBasePath() : isProd = " + isProd);
console.log("getBasePath() : basePath = " + basePath);
return basePath
}
module.exports = withCSS(withImages({
assetPrefix: getBasePath() ,
publicRuntimeConfig: {
basePath: getBasePath() ,
},
}));
Static images
Use "next-images" in order to import the images and reference the imported object in the 's src tags
Change any references to your static images (those in /public folder) to have the base path prefix. For example my "Footer" component has the following
import '../stylesheets/main.css';
import img1 from '../public/image_name1.png'
import img2 from '../public/image_name2.png'
export default class o extends React.Component {
render(){
var prefix = publicRuntimeConfig.basePath
return (
<div >
<a className="icon" href="http://www.image_name.com" >
<img src={img1} alt="image_name1"/>
</a>
<a className="icon" href="http://www.image_name2.com">
<img src={img1} alt="image_name2"/>
</a>
</div>
);
}
}
Note: I tried to use the publicRuntimeConfig.basePath as a prefix to the src URL (as below), but this did not work in my deployed environment (see below)
import getConfig from 'next/config'
const { publicRuntimeConfig } = getConfig()
...
...
<a className="icon" href="http://www.image_name.com" >
<img src={`${publicRuntimeConfig.basePath}/image_name1.png`} alt="image_name1"/>
</a>
Links
Change your Link's to use the base path prefix, for example in my "Header" component i have the following
import Link from 'next/link';
import '../stylesheets/main.css';
import getConfig from 'next/config'
const { publicRuntimeConfig } = getConfig()
const detailId1 = "banana"
const Header = () => (
<div>
<div>
<Link href={`${publicRuntimeConfig.basePath || ''}/`}>
<a className="linkStyle">Home</a>
</Link>
<Link href={`${publicRuntimeConfig.basePath || ''}/about`} >
<a className="linkStyle">About</a>
</Link>
<Link href={`${publicRuntimeConfig.basePath || ''}/details/[id]`}
as= {`${publicRuntimeConfig.basePath || ''}/details/${detailId1}`} >
<a className="linkStyle">Details Var 1</a>
</Link>
</div>
</div>
);
export default Header;
Note: In the blog https://levelup.gitconnected.com/deploy-your-nextjs-application-on-a-different-base-path-i-e-not-root-1c4d210cce8a, it contains a "Link.tsx" that does the adding of the prefix for you, so you simply use that Link component (import Link from "./Link.tsx";) and not the nextJS version (import Link from 'next/link';). However, that "Link.tsx" does not work for me when I have variables in my link URLs.
Running your nextjs app
When running your application locally when you do NOT want a base path you can just running
npm run dev
As no BASE_PATH is specified your application should be accessible from "http://localhost:3000" and your src values should be "/image_name1.png" and when you hover over your s you will see the link is "http://localhost:3000/pagename"
When you want to run with a base path do the following
export BASE_PATH=a/b
npm run dev
Note: for some reason in my environment if i specify "export BASE_PATH=/a/b" (/ at the start of the path) I get a folder directory added to the beginning of the path. Therefore i specify it without the starting / and the code in next.config.js adds the starting / if need be.
You can not access your app at "http://localhost:3000" as you have the base path/assetPrefix/publicRuntimeConfig.basePath set. Now you need a reverse proxy.
NGINX : Reverse Proxy
I found the easiest setup was to use a NGINX docker image. You need to run NGINX with a configuration containing the redirection to your NextJS app.
Create a folder and add in that folder a "default.conf" file. Make sure the path you put in your "location" is the SAME path you specified for BASE_PATH when starting your nextjs app.
server {
listen 80;
server_name localhost;
location /a/b/ {
proxy_pass http://myhost:3000/;
}
}
Important Notes:
you have to have the trailing / on the end of your proxy_pass URL otherwise additional paths are not passed onto your NextJS apoplication
if you use a variable in the location you must make sure you include passing on the paths
example
location ~ /a/b/(.*)$ {
set $upstream http://myhost:3000/$1;
proxy_pass $upstream;
}
In a command prompt from that directory run a NGINX docker image, telling it to use your config file.
docker run --name mynginx1 -v C:/zNGINX/testnginx/conf:/etc/nginx/conf.d -p 80:80 -d nginx
name of the docker container is "mynginx1"
the v parameter is telling it to copy any files in "C:/zNGINX/testnginx/conf" on your computer to the "/etc/nginx/conf.d" directory in the docker container. This will copy your "default.conf" to the docker container and NGINX will read that configuration file.
Note: Make sure you have the "conf.d" in your path for the docker location (":/etc/nginx/conf.d"), blogs I read did not include this part, it only specified ":/etc/nginx/", and without it the image doesn't start.
the p parameter is telling to run NGINX on port 80
Go to the following URL
http://localhost:80/a/b/
NGINX will redirect that URL to "http://localhost:3000". Therefore your application should now be accessible from the URL with the base path. Clicking on s should work, the link should contain the base path which goes to NGINX which redirects back to the application stripping off the base path leaving any other paths.
Real World Server Deployment using Docker
If you are deploying your application to a server, as apposed to running locally, you can build your application and then copy the relevant files/folders to the server machine. Make sure you have the BASE_PATH set when both building and running your app
export BASE_PATH=a/b
npm run build
cp package*.json [server_location]
cp next.config.js [server_location]
cp ./next [server_location]
then on that server location run
npm install
export BASE_PATH=a/b
npm run start
Note: If you have images in "public" that you reference in your app, use "next-images" and import the image rather than use the publicRuntimeConfig.basePath as a prefix. When i did the latter the images were not found. See the section about about images for examples.
To add to the answers here, simply using basePath is not enough. basePath works very well for automatically pointing links, but it does not do anything to static files served from public directory.
For example you have public/img/my-img.png that you referred in your img or Image element as <img src="img/my-img.png" /> or <Image src="/img/my-img.png" />, you have to change it to <img src="your-sub-path/img/my-img.png" /> or <Image src="/your-sub-path/img/my-img.png" /> respectively.
You can use custom server to create NextJS application work on your specific URL:
See the example here:
https://github.com/zeit/next.js/tree/canary/examples/custom-server-express
The key point is to add your specific url as an API, then forward user's request to your specific page you want to serve:
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()
app.prepare().then(() => {
const server = express()
server.get('/my-test-application/path-for-my-app', (req, res) => {
return app.render(req, res, '/index', req.query)
})
server.get('/my-test-application/path-for-my-app/page1', (req, res) => {
return app.render(req, res, '/page1', req.query)
})
server.get('/posts/:id', (req, res) => {
return app.render(req, res, '/posts', { id: req.params.id })
})
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
To me, all the solutions mentioned is too much trouble for me, so i rolled the following way.
next.config.js
module.exports = {
basePath: '/drh',
}
_app
You can overwrite the _app.js file.
export default function App({ Component, pageProps }) {
return <Component {...pageProps} base="/drh" />
}
This way all pages will have a prop base hard coded.
export default function Index({ base }) {
return <img src={`${base}/images/hooks-logo.png`}/>
}
context
if you don't want to push this base to all children using a prop, this is the place you can use a context. The context provider can be setup in the _app.js as well.
seems to me one of a good entry point to nextjs app is _app.js, verse a typical react app is index.js.
I solved it like this, by writing a redirect in next.config.js:
const withImages = require('next-images')
module.exports = withImages(withSass({
cssLoaderOptions: {
url: false
},
//Might need to change it here, before going to the production environment.
assetPrefix: 'http://localhost:3000',
postcssLoaderOptions: {
config: {
ctx: {
theme: JSON.stringify(process.env.REACT_APP_THEME)
}
}
}
}));

Why Node.js only recognizes absolute paths? [duplicate]

This question already has answers here:
What is the difference between __dirname and ./ in node.js?
(2 answers)
Closed 3 years ago.
I created a file called nodes, then initialized the file with npm init and the main js file is called main.js. I also created index.html and index.css in the file, after that I want to use Node.js Render this index.html, so I wrote in main.js:
const http = require('http');
const fs = require('fs');
const hostname = '127.0.0.1';
const port = 9000;
const mainHTML = './index.html';
const server = http.createServer((req, res) => {
fs.stat(`./${mainHTML}`, (err, stats) => {
if(stats) {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
fs.createReadStream(mainHTML).pipe(res);
}
});
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
I opened the server with the node desktop/nodes command, but node.js could not find the file.
Until I changed the relative path to an absolute path, Node.js will recognize it:
const mainHTML = 'desktop/nodes/index.html';
Why is this? If I want to use a relative path, how do I do it?
When you access a file in node.js with a relative path, the file is accessed relative to the value of the current working directory for the process. Note, in the modular world of node.js, the current working directory may or may not be the same as the directory where your module was located. And, your code can change the current working directory to be whatever you want it to be.
It is common in modular node.js code to have a programming desire to access things relative to the directory where the current module's code was loaded from. This gives you the ability to use relative paths so the app/module can work anywhere, but it gives you certainty that you'll get the files you want. To do this, one typically uses the module-specific variable __dirname. This is the directory that the current module was loaded from. If it's the main script that node.js was started with then, it's the directory of that script.
So, to get a file from the same directory as the script you are current in, you would do this:
const mainHTML = 'index.html';
fs.createReadStream(path.join(__dirname, mainHTML)).pipe(res);
To access a file in a subdirectory public below where the script is, you could do this:
const mainHTML = 'public/index.html';
fs.createReadStream(path.join(__dirname, mainHTML)).pipe(res);
To access a file in a different subdirectory at the same level (common parent directory) as where the script is, you could do this:
const mainHTML = '../public/index.html';
fs.createReadStream(path.join(__dirname, mainHTML)).pipe(res);
All of these use paths that are relative to where the script itself is located and do not depend upon how the module/script was loaded or what the current working directory of the app is.
You are creating http server, which creates it's path as base, so it understands only paths taking that base path as relative path. If you want to use relative path, then you need to resolve that path.
You can use 'path' library.
const path = require('path')
// To resolve parent path
path.resolve('..', __dirname__)

__webpack_public_path__ does not work after reloading the page

We are working on a Vue.js application which is built with webpack. The requirement is that our application not only has to be accessible via the base path / but also via a custom path, so that the application could be accessable next to other services. With webpacks \__webpack_public_path__ I was able to set the base path on the fly regarding a specified base path, like if the current path has got /foo/ in it, the base path should start at .../foo/.
While navigating through the application everything looks good, as long as I open the app at the base path. The problem is that after I navigate to any sub path and reload the page, no files can be found. The server will first provide the index.html and there I need to use relative paths, since the base path must be determined dynamically. But if any other than the base path is opened, the relative paths in the index.html won't point to the correct bundle.js and the \__webpack_public_path__ will not be set...
Currently we have got two entry points in our webpack config. The first one is a JS file where we the global variable \__webpack_public_path__ will be set relatively based on the current visited URL. So lets say the base path should start at /foo/ and the visited URL is https://www.host.com/some/path/foo/sub/sub1. Therefore the relatively base path will be /some/path/foo/ and assets will be found at i.e. /some/path/foo/assets. All other paths used in the webpack config, like in the file-loader or any other plugin, are relative.
// webpack.config.js
...
module.exports = (env, argv) => ({
...
entry: ['#babel/polyfill', './src/main/js/constants/webpack-public-path.js', './src/main/js/index.js'],
output: {
filename: 'assets/js/bundle.js',
path: path.resolve(__dirname, relativeOutputPath),
publicPath: '/'
},
module: {
...
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'file-loader',
options: {
name: 'assets/fonts/[name].[ext]'
}
}
...
},
...
});
// webpack-public-path.js
__webpack_public_path__ = (() => {
const BASE_PATH = '/foo/';
let path = window.location.pathname;
return path.includes(BASE_PATH) ? path.substring(0, path.indexOf(BASE_PATH) + BASE_PATH.length) : '/';
})();
Has anyone ever faced a similar problem? Is it possible, that the relative paths in the index.html can be defined afterwards or maybe are defined at build time by webpack?
I got a solution for my problem. What I had to do was to load all necessary assets manually in the index.html. Unfortunatly I have to maintain the determination of the dynamic base path on two different files (index.html and the webpack-public-path.js) but at least it's working now. So in the index.html I insert respective tags into the DOM manually and therefore load all needed assets based on the determined base path.

What is the rule for express.static root path?

I ran
node src/app.js
in the mean-to directory
express.static('public')
would work,why?
don't need to specify the path?
what's the rule?
and I know
__dirname+'/../public'`
works just fine.
Just want to make sure the the logic here
I look up the doc
http://expressjs.com/en/starter/static-files.html
it says
"Pass the name of the directory that contains the static assets to the express.static middleware function to start serving the files directly. "
"the path that you provide to the express.static function is relative to the directory from where you launch your node process"
Does that mean
if I run node src/app.js in mean-to folder --> use express.static('public')
if I run node app.js in src folder => use express.static('../public')
and for safety, better use __dirname to get the absolute path of the directory
Express static uses resolve function from module path
Example using path:
var p = require('path');
p.resolve('public'); // will return your absolute path for `public` directory
So rules for express static the same as for path#resolve function
You can see more example in the docs for path module
What's the difference between p.resolve and __dirname?
path#resolve
p.resolve() resolves to an absolute path
path.resolve('public');
Will return your absolute path for public' directory(e.g "C:\projects\myapp\public")
path.resolve('src'); // => "C:\projects\myapp\src")
__dirname
__dirname - name of the directory(absolute path) from which you're currently running your app
file app.js
console.log(__dirname);
Running node app.js will print your absolute path for your app, e.g "C:\projects\myapp"
You mixed 2 lines in one... And both are necessary:
//This line is to say that the statics files are in public
app.use(express.static(__dirname + "/public"));
//This another line is to say that when call the route "/" render a page
app.get("/", function (req, res) {
res.send('Hello World!');
}
Probably, the reason is in running it from directory highter one level than dir with code? Try run it from src:
cd src
node app.js

Categories