Angular 6+ deploy to Apache server by solving 404 Not found error on page refresh

This article will hep you to deploy angular 6+ application on apache server also to solve 404 not found error on page refresh.


Getting Started

For now let's hope you have already setup a new Angular6+ project or has an existing Angular6+ project.Here we are considering working with an existing Angular6+ App. I have named my project as angular-deployemnt-sample.

The project was scaffolded using Angular cli which can be used to generate basic app structure for your projects.


After generating the basic app using Cli and setting up basic routing module and a home component the project structure looks something like this


if you want to run the same in local just use the command

ng serve --open  

The above command will start serving the project at the url

http://localhost:4200/  



Build app for production

Before deploying to the apache server we need to build the project and optimise it for production.

ng build --prod  


Setting our Base

To link around your application using relative links, you will need to set a <base> in the <head> of your document. while setting base you will have to consider where the projects resides in the server

In angular 6+ cli allows you to set the base automatically while building the project.if your project files is placed in the /var/www/html folder (that is project files copied from dist folder after ng build --prod to root folder of your server) or you are running on ng serve (while under development in local) then your base should be

<base href="/">  

if your project files resides in some folder somefolder then base should be

<base href="/somefolder/">  

To set the base during build to root folder /var/www/html you can use the regular command

ng build --prod  

To set the base during build when your project files resides in some folder somefolder then use

ng build --prod  --base-href somefolder  

Now after you copying the files from your dist folder to apache servers root folder /var/www/html the project will work until you refresh the page

404 Not found error on page refresh

Now that you have deployed the project to server, and everything seems ok until you try to refresh the page . You will find that your app throws a 404 Not found error


Solution

To solve this there are two approaches or strategy


We will use PathLocationStrategy

The default strategy used in Angular is the PathLocationStrategy so we need to do nothing to enable it. And this will be the statergy that we are going to use here

It takes advantage of a relatively new HTML5 API called pushstate (from the HTML5 history API).

By using pushstate we can change the URL and not have the browser request the page from the server and without needing to use a hash fragment.

Unfortunately it has one big downside, if we then reloaded the page or bookmarked and opened it later the browser would make a request to the server

By using a hash fragment the server never needs to know about any application URL, it will only ever get asked for the root page and it will only ever return the root page.

But by using a PathLocationStrategy the server needs to be able to return the main application code for every URL, not just the root URL.

So with PathLocationStrategy we need to co-operate with a server side that supports this functionality, it’s possible and quite easy to implement a server side like this but it does require some effort and cooperation.

When you have html5Mode enabled, the # character will no longer be used in your URLs. The # symbol is useful because it requires no server side configuration. Without #, the URL looks much nicer, but it also requires server side rewrites.

Configuring Apache Server

we need to configure the server for rewrites, and this involve following steps

  • Activate mod_rewrite
  • Edit Apache configuration file
  • Restart Apache
  • Setting Up .htaccess

First we need to activate mod_rewrite. It's available but not enabled with a clean Apache 2 installation

You can use the following command

sudo a2enmod rewrite  

This will activate the module or alert you that the module is already enabled.

By default, Apache prohibits using an .htaccess file to apply rewrite rules, so first you need to allow changes to the file. Open the default Apache configuration file using nano or your favourite text editor.

sudo nano /etc/apache2/sites-available/000-default.conf  

Inside that file, you will find a <VirtualHost *:80> block starting on the first line. Inside of that block, add the following new block so your configuration file looks like the following. Make sure that all blocks are properly indented.

<VirtualHost *:80>  
    <Directory /var/www/html>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Require all granted
    </Directory>

</VirtualHost>  

Now Save and close the file .To put these changes into effect, restart Apache

sudo systemctl restart apache2  

mod_rewrite is now fully enabled. In the next step we will set up an .htaccess file in the root folder /var/www/html where our angular files are placed and we we'll use it to define rewrite rules for redirects

 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]  

Also since we need to increase SEO points, i like to add some more code to .htaccess to enable compression and leverage browser Caching

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]


# Enable Compression
<IfModule mod_deflate.c>  
  AddOutputFilterByType DEFLATE application/javascript
  AddOutputFilterByType DEFLATE application/rss+xml
  AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
  AddOutputFilterByType DEFLATE application/x-font
  AddOutputFilterByType DEFLATE application/x-font-opentype
  AddOutputFilterByType DEFLATE application/x-font-otf
  AddOutputFilterByType DEFLATE application/x-font-truetype
  AddOutputFilterByType DEFLATE application/x-font-ttf
  AddOutputFilterByType DEFLATE application/x-javascript
  AddOutputFilterByType DEFLATE application/xhtml+xml
  AddOutputFilterByType DEFLATE application/xml
  AddOutputFilterByType DEFLATE font/opentype
  AddOutputFilterByType DEFLATE font/otf
  AddOutputFilterByType DEFLATE font/ttf
  AddOutputFilterByType DEFLATE image/svg+xml
  AddOutputFilterByType DEFLATE image/x-icon
  AddOutputFilterByType DEFLATE text/css
  AddOutputFilterByType DEFLATE text/html
  AddOutputFilterByType DEFLATE text/javascript
  AddOutputFilterByType DEFLATE text/plain
</IfModule>  
<IfModule mod_gzip.c>  
  mod_gzip_on Yes
  mod_gzip_dechunk Yes
  mod_gzip_item_include file .(html?|txt|css|js|php|pl)$
  mod_gzip_item_include handler ^cgi-script$
  mod_gzip_item_include mime ^text/.*
  mod_gzip_item_include mime ^application/x-javascript.*
  mod_gzip_item_exclude mime ^image/.*
  mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</IfModule>

# Leverage Browser Caching
<IfModule mod_expires.c>  
  ExpiresActive On
  ExpiresByType image/jpg "access 1 year"
  ExpiresByType image/jpeg "access 1 year"
  ExpiresByType image/gif "access 1 year"
  ExpiresByType image/png "access 1 year"
  ExpiresByType text/css "access 1 month"
  ExpiresByType text/html "access 1 month"
  ExpiresByType application/pdf "access 1 month"
  ExpiresByType text/x-javascript "access 1 month"
  ExpiresByType application/x-shockwave-flash "access 1 month"
  ExpiresByType image/x-icon "access 1 year"
  ExpiresDefault "access 1 month"
</IfModule>  
<IfModule mod_headers.c>  
  <filesmatch "\.(ico|flv|jpg|jpeg|png|gif|css|swf)$">
  Header set Cache-Control "max-age=2678400, public"
  </filesmatch>
  <filesmatch "\.(html|htm)$">
  Header set Cache-Control "max-age=7200, private, must-revalidate"
  </filesmatch>
  <filesmatch "\.(pdf)$">
  Header set Cache-Control "max-age=86400, public"
  </filesmatch>
  <filesmatch "\.(js)$">
  Header set Cache-Control "max-age=2678400, private"
  </filesmatch>
</IfModule>  


Now if you refresh your app in browser the app will work fine