Handle the MEAN-stack application address without fragment '#'

Edit 1: Here is a mini code I made that reproduces the error. Please follow README.md to install.

Edit 2: Finally, I found one solution. Besides $locationProvider.html5Mode(true); ($locationProvider.hashPrefix('') is NOT necessary for me) and <base href="/" />, I need to add the follows in routes/index.js, rather than app.js. Then, we do NOT need to add anything more to app.js or nginx or apache like this thread mentions.

var express = require('express');
var router = express.Router();
var path = require('path');
... ...
router.get('*', function(req, res) {
    res.sendfile('./views/index.html'); // load our public/index.html sendFile
    // res.sendFile('index.html', { root: path.join(__dirname, 'views') }); // does not work
});

One problem is, in the server console, it gives express deprecated res.sendfile: Use res.sendFile instead routes/index.js:461:9. But res.sendFile('index.html', { root: path.join(__dirname, 'views') }); can not help, it returns 404 error.

My express version is ~4.14.0... does anyone know how to fix that?

OP:

I develop in Mac with Apache a MEAN-stack application that can be requested by https://localhost:3000/#/home. In production with an NGINX server, the application can be requested by https://www.myapp.io/#/home. The fragment-identifier # is needed in all cases because of angular ui-router.

So I wanted to make pretty url without # (eg, https://www.myapp.io/home, https://localhost:3000/home) work. I have done the following:

  1. added $locationProvider.html5Mode(true); $locationProvider.hashPrefix('') in app.config(['$stateProvider'....

  2. added <base href="/" /> in index.html

As a result, https://localhost:3000/#/home changes automatically to https://localhost:3000/home in the browser bar, similarly for https://www.myapp.io/#/home.

However, directly entering https://localhost:3000/home or https://www.myapp.io/home in the browser will raise an error (I don't know how to turn previous <h1><%= message %></h1><h2><%= error.status %></h2><pre><%= error.stack %></pre> in error.ejs to error.html, so I don't have more details).

So now, the goal is to make https://localhost:3000/home and https://www.myapp.io/home work.

By following this thread, I added the follows to app.js:

app.use('/js', express.static(__dirname + '/js'));
app.use('/dist', express.static(__dirname + '/../dist'));
app.use('/css', express.static(__dirname + '/css'));
app.use('/partials', express.static(__dirname + '/partials'));    
app.all('/*', function(req, res, next) {
    res.sendFile('index.html', { root: __dirname });
});

And in Apache of Mac, here is my httpd-vhosts.conf, after restarting apache, https://localhost:3000/home still returns an error.

<VirtualHost *:443>
    ServerName localhost
    DocumentRoot "/Users/SoftTimur"

    SSLEngine on
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
    SSLCertificateFile /etc/apache2/ssl/localhost.crt
    SSLCertificateKeyFile /etc/apache2/ssl/localhost.key

    <Directory "/Users/SoftTimur">
        RewriteEngine on

        # Don't rewrite files or directories
        RewriteCond %{REQUEST_FILENAME} -f [OR]
        RewriteCond %{REQUEST_FILENAME} -d
        RewriteRule ^ - [L]

        # Rewrite everything else to index.html to allow html5 state links
        RewriteRule ^ index.html [L]

        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    </Directory>
</VirtualHost>

In production, here is the NGINX server block. After restarting NGINX, https://www.myapp.io/home still returns an error.

server {
    listen 443 ssl;

    server_name myapp.io www.myapp.io;

    ssl_certificate /etc/letsencrypt/live/myapp.io/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.io/privkey.pem;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:EC$
    ssl_session_timeout 1d;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;

    index index.html;

    root /opt/myapp;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location ~ /.well-known {
        allow all;
    }

    location / {
        proxy_set_header    Host                $host;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
        proxy_set_header    Accept-Encoding     "";
        proxy_set_header    Proxy               "";
        proxy_pass          https://127.0.0.1:3000;

        # These three lines added as per https://github.com/socketio/socket.io/issues/1942 to remove sock$

        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
    }
}

Could anyone help?

Answers:

Answer

try this in your express server

var express = require('express');
var app = express();

app.get('*', function (req, res) { res.sendFile(__dirname + '/views/index.html'); });

and in your angular app:

$locationProvider.hashPrefix('!').html5Mode({
    enabled: true
});
$urlRouterProvider.otherwise('/');

and you still need the <base href="/"> in your index.html

let me know if this works for you

EDIT:

I just found your app in https://github.com/chengtie/mini-mean, looks like your app.use order is wrong. Please copy paste this in your express server and check if it's okay now. pastebin

Answer

This might useful stuff,

AngularJS routing without the hash '#'

Also, use this line in your express server file. app.use(express.static(path.join(__dirname, 'client folder')));

this will directly finds your index.html file in that views folder and loads it

Answer
  1. You don't need Apache or Nginx to run NodeJs in development, just node server.js is enough
  2. Express gave you that error because you are using a deprecated API res.sendfile please use res.sendFile (capital F)
  3. Some info for doing SPA:
    • When you have the '#' in your URL, the browser interpret it as a local reference and thus, won't send new request to the server
    • By enabling $locationProvider.html5Mode(true) you are now using html5 push state to navigate around your application history and (if I'm not mistaken you are happening to be using) angular effectively remove the '#' in url
    • Without the '#' (hash-bang) browser will interpret it as a new request and send it to server, so you have to map all requests from server to your SPA entry file
    • For exact steps of replicating this behavior, consult this article: https://scotch.io/tutorials/pretty-urls-in-angularjs-removing-the-hashtag (the base href in your entry file is important)
Answer

If it's just about the #. You can remove it in Angular itself.

Just inject locationProvider at your app entry and set htmlMode to true. In your index.html set the baseUrl.

$locationProvider.html5Mode(true)

And in you index.html add:

<base href="/" />

That will generate your urls without the #. Does that help?

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.