I have an AngularJS app built on top of Node.JS that is hosted in Azure (IIS) and uses HTML5 mode. In situations where I don't use Node.JS and just use AngularJS with IIS I can rewrite URLs so that a page refresh doesn't cause a 404 error using the following rule in the IIS Web.config:
<rule name="AngularJSHTML5Mode" stopProcessing="true">
<match url=".*"/>
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true"/>
</conditions>
<action type="Rewrite" url="/"/>
</rule>
However, when using Angular on top of Node.js (with ExpressJS) I get an error such as "Cannot GET /myroute" on page refresh, even with the above rule in IIS. Is there something I need to configure in Server.js or a different rule in Web.config.
Here is what I have in Express at the moment. I am using Express 4:
// Initialize Express App
var app = express();
var listener = app.listen(1337, function () {
console.log('Listening on port ' + listener.address().port); //Listening on port 1337
});
app.use(express.static(__dirname + "/public"));
I understand that page refreshes shouldn't happen in a SPA as a matter of practice, but they do, and I need to account for this.
I've experimented and this is what has worked for me:
web.config (site.js is the entry point to my node app)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpErrors existingResponse="PassThrough" />
<iisnode nodeProcessCommandLine=""c:\program files\nodejs\node.exe"" watchedFiles="*.js"
interceptor=""%programfiles%\iisnode\interceptor.js"" promoteServerVars="LOGON_USER"/>
<handlers>
<add name="iisnode" path="site.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<clear />
<rule name="default">
<match url="/*" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="Rewrite" url="site.js" />
</rule>
</rules>
</rewrite>
<security>
<requestFiltering>
<hiddenSegments>
<add segment="node_modules" />
</hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
</configuration>
static file server in express + url rewrite
app.use(express.static(clientDir));
// for SPA
app.get('/*', function(req,res, next){
res.format({
html: function(){
res.sendFile(path.join(clientDir, 'index.html'));
},
json: function(){
next();
}
});
});
//routes...
I have also tried to implement url rewrite with virtual app in iis. This doesn't work. Instead, I had to configure a path within my node module to strip the leading part of the route.
However, you can have iis serve the static files once your app has bootstrapped.
Upon further digging, I didn't need the Web config. I have this in my server.js to institute the connection to the Angular index file:
var app = express();
app.use(express.static(__dirname + "/public"));
var listener = app.listen(1337, function () {
console.log('Listening on port ' + listener.address().port); //Listening on port 1337
});
This part is critical, at the end of all your GETs, POSTs, etc. I added this as the last function. It needs to be last since it is a catch all and if you put it before a GET or POST function it won't see it as Express but a broken Angular route, and if your App.js is configured correctly, will route to the homepage (not desired):
// Placed at end for routing non-Express calls/AngularJS refreshes
app.use('/*', function (req, res) {
res.sendFile(__dirname + '/public/index.html');
});
Related
I have a node server like this:
var express = require('express');
var fs = require('fs');
var path = require('path');
var root = fs.realpathSync('.');
var app = express();
app.configure(function () {
app.use(express.static(__dirname + '/./'));
app.use(app.router);
});
app.get('/*', function (req, res) {
res.sendfile(path.join(root, './index.html'))
});
/*app.get('/dashboard', function (req, res) {
res.sendfile(path.join(root, 'index.html'))
});*/
app.listen(3000);
console.log('Listening on port 3000');
On the frontend, I am using backbone routes to route the application modules using HTML History API. So its always a well formatted URL. The problem occurs when I refresh the page. It works fine in local node server but when I deploy the application on microsoft azure cloud, the refresh creates a problem and it says, it cannot find a resource.
Is there anything specific to azure that I need to configure to make it understand that it should serve index.html on new requests instead of looking up for the resource?
In apache, we use .htaccess to do so but I have no idea about how to do the same on azure cloud!
Found the solution:
You need to have a webconfig file in the root for this to work. Azure internally has nothing but IIS to serve the files. If you observe closely, server.js is of no use once you deploy the application. IIS takes the control to serve the index.html file and hence you need to do something in the IIS configuration. Here is the Web.config (case sensitive) file that you need to put in the application root:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="redirect all requests" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" pattern="" ignoreCase="false" />
</conditions>
<action type="Rewrite" url="index.html" appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
You might try just use()ing a general middleware instead of a method-specific route:
app.use(function (req, res) {
res.sendfile(path.join(root, './index.html'));
});
As far as I know, Express currently does not support method-specific middleware (e.g. app.get(function(req, res) { ... })).
Also on an unrelated note, var root = fs.realpathSync('.'); is unnecessary since the global __dirname should get you the same value.
Images like png are successfully displayed on app if locally run next build + next start but after deploying it to Azure Web App via server.js and a web.config, images are not loading anymore.
I have my image under public/imgs/logo.png
and I reference it like this:
<Image src={'/imgs/logo.png'} height={'20px'} width={'180px'} />
I even tried running the server.js directly(node server.js) once everything is built and the image still loads. But once I upload the build files(.next folder), web.config, and server.js in my Azure Web App, the image is not loading anymore.
Please help. Below are some of the files I used to set this up.
server.js
const { createServer } = require("http");
// const express = require('express') (Only if you app uses express)
const next = require("next");
const port = process.env.PORT || 8888;
const isDev = process.env.ENV !== "production";
const app = next({ isDev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => {
handle(req, res);
}).listen(port, (err) => {
if (err) throw err;
console.log(`Ready on http://localhost:${port}`);
});
});
web.config
<?xml version="1.0" encoding="utf-8"?>
<!--
This configuration file is required if iisnode is used to run node processes behind
IIS or IIS Express. For more information, visit:
<https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config>
-->
<configuration>
<system.webServer>
<!-- Visit <http://blogs.msdn.com/b/windowsazure/archive/2013/11/14/introduction-to-websockets-on-windows-azure-web-sites.aspx> for more information on WebSocket support -->
<webSocket enabled="false" />
<handlers>
<!-- Indicates that the server.js file is a node.js site to be handled by the iisnode module -->
<add name="iisnode" path="server.js" verb="*" modules="iisnode"/>
</handlers>
<rewrite>
<rules>
<!-- Do not interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^server.js\\/debug[\\/]?" />
</rule>
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}"/>
</rule>
<!-- All other URLs are mapped to the node.js site entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="server.js"/>
</rule>
</rules>
</rewrite>
<!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
<security>
<requestFiltering>
<hiddenSegments>
<remove segment="bin"/>
</hiddenSegments>
</requestFiltering>
</security>
<!-- Make sure error responses are left untouched -->
<httpErrors existingResponse="PassThrough" />
<!--
You can control how Node is hosted within IIS using the following options:
* watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server
* node_env: will be propagated to node as NODE_ENV environment variable
* debuggingEnabled - controls whether the built-in debugger is enabled
See <https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config> for a full list of options
-->
<iisnode watchedFiles="web.config;*.js"/>
</system.webServer>
</configuration>
next.config.js
const path = require("path");
require("dotenv").config();
const withImages = require("next-images");
module.exports = withImages({
reactStrictMode: true,
eslint: {
dirs: ["apiclients", "common", "components", "config", "pages", "stores"],
},
sassOptions: {
includePaths: [
path.join(__dirname, "styles"),
path.join(__dirname, "components"),
],
prependData: `#import "styles/_variables";`, // prepend _css variables in all css documents
},
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
config.plugins.push(new webpack.EnvironmentPlugin(process.env));
return config;
},
experiments: {
asset: true,
},
});
The (next/image) component relies on the Next Server running. If you export it in any kind of static configuration being hosted by another service, it fails miserably (unless you do some custom coding to handle the images). I created a php program that redirects images from the /_next/assets/ folder in my circumstances, but what you should also test, is replace one of the <Image tags with an <img tag, and see if that one image displays correctly. If it does, then that's your issue. You could then replace all your tags, but you would loose some advantages such as lazy loading, etc.
Site is developed in AngualrJS and deployed on IIS web server. Where as we have only on page i.e. Index.html since it is single page application. For SEO purpose we are using prerender.io.
Added following code in web.config file to rewrite every single URL to root path
<rule name="AngularJS" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
Also handled all invalid URL into main.module.js with following code which will redirecting to error page.
$urlRouterProvider.otherwise(function ($injector, $location) {
var $state = $injector.get('$state');
var $rootScope = $injector.get('$rootScope');
var now = new Date(); now.setTime(now.getTime() + 1 * 1800 * 1000);
setContentCookie('dlerrorURL', $location.$$absUrl, now.toUTCString(), 'xyz.com')
$rootScope.error = true;
$state.go('app.newerror')
});
With the help of above code, status code 404 getting appeared on prerender.io, but if we look into netowrk tab in developer console it displays 200 status code for all invalid URL. Technically it is correct since we have added rule in web.config file to rewrite all the URL to root path.
After lots of finding we came to the solution that we have to keep all the URLs at server side by implementing Asp.Net MVC code and adding all routes into route.config file. So that if the URL is not matched it will return 404 status code.
But since we are having 300+ pages we are avoiding to implement the above solution. Is there any alternat way to get or set 404 status code for invalid URL.
As per my experience so far, I faced the same issue and we made few changes like this and it worked like charm for us!
Follow this steps:
Step 1:
Go to your RouteConfig.cs in your MVC application
you will see some route map like this..
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Now comment that default route and add an additional map route like this
routes.MapRoute(
name: "Home",
url: "dv/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "CatchAll",
url: "{*any}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
in above Map route name as HOME you will see something like dv you can replace it with any keyword for you like api or any initial of your project like abc or xyz we are doing this just to seperate the server route with angular route.
REMOVE THE CODE YOU HAVE WRITTEN IN YOUR WEB CONFIG FOR URL REWRITING.
FOR PRERENDER:
Add something like this in your web.config
<configSections>
<section name="prerender" type="Prerender.io.PrerenderConfigSection, Prerender.io, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</configSections>
<prerender token="YOUR_TOKEN_HERE"></prerender>
and add prerender.io DLL to your MVC project.
Now, in system.web add something like this for prerender.
<system.web>
<httpModules>
<add name="Prerender" type="Prerender.io.PrerenderModule, Prerender.io, Version=1.0.0.2, Culture=neutral, PublicKeyToken=null"/>
</httpModules>
</system.web>
Now in the App_Start folder of your MVC application add this class name PreApplicationStartCode.cs.
public static class PreApplicationStartCode
{
private static bool _isStarting;
public static void PreStart()
{
if (!_isStarting)
{
_isStarting = true;
DynamicModuleUtility.RegisterModule(typeof(Prerender.io.PrerenderModule));
}
}
}
And in your AssemblyInfo.cs under Properties of your MVC app add something like this for prerender at the last line of the file.
[assembly: PreApplicationStartMethod(typeof(yourappnamespace.PreApplicationStartCode), "PreStart")]
i have enabled the html5mode to remove Hashing from web url,after that i get an error.so i have mentioned the base href in master page. set rewrite url in web config.
when i goes into state view page and refresh the page it will show the wrong url.when launch the application in iis 7.5 getting error.
for that i have rewrite the url.
<rewrite>
<rules>
<rule name="RewriteRules stopProcessing="true">
<match url=".*"/>
<conditions logicalGrouping="MatchAll">
<add input ="{REQUEST_FILEName}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILEName}" matchType="IsDirectory" negate="true"/>
<add input ="{REQUEST_URI}" pattern="^/(api)" negate="true"/>
</conditions>
<action type="Rewrite" url="home/home"/>
</rule>
</rules>
</rewrite>
</system.webServer>
Can anyone helpme
when you enable html5mode, your all request should redirected to your main url (where your application start ex. home/index). try add below code in the RegisterRoutes method
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default1",
url: "{*.}",
defaults: new
{
controller = "Home",
action = "Index",
}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I am creating a new Web API and Angular application. I want to know how we can handle routes on server side. Everything is working fine. I am using ui-router for routing, when i refresh browser that its giving me.
HTTP Error 404.0 - Not Found
I want to know how we can handle this.
Code for ui-router
$stateProvider.state('login', {
url: '/',
templateUrl: templatesDirectores.wmAccount + 'login.html',
controller: 'login-controller',
controllerAs: 'vm'
})
.state('register', {
url: '/register',
templateUrl: templatesDirectores.wmAccount + 'register.html',
controller: 'register-controller',
controllerAs: 'vm'
})
.state('forgetPassword', {
url: '/forgetpassword',
templateUrl: templatesDirectores.wmAccount + 'forget-password.html',
controller: 'forget-password-controller',
controllerAs: 'vm'
})
The view is loading fine as per the configuration, but when the URL is localhost:1235/register, it's loading fine first time but when I hit the refresh button I get 404 error.
This is occurring because you are using HTML5Mode = true in your Angular Application. Angular uses "hash" (/#register) routes to handle it's routing by default, in order to ensure that the browser does not perform a page reload from the server.
Using HTML5Mode, you can suppress the #, but at a cost. The browser must be HTML5 compliant, because HTML5Mode uses HTML Push State (HistoryAPI).
Also, your server must be configured to handle requests for routes which may exist in Angular but do not exist on the server. When you directly load a page, either by typing it in or by using refresh, a request is sent to the server, with a URL that the server may not know how to handle. Your server should be configured to return your Index.html page for any routes it does not expressly handle. This configuration varies per server.
Given you stated you are using IIS to host your site, something like the following would be necessary:
Web.config:
<system.webServer>
<rewrite>
<rules>
<rule name="Main Rule" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
Essentially, for all routes (.*), if the request is not a file or a directory, rewrite the url to return the base URL /.
This can also be done in code, something similar to this:
Global.asax:
private const string ROOT_DOCUMENT = "/default.aspx";
protected void Application_BeginRequest( Object sender, EventArgs e )
{
string url = Request.Url.LocalPath;
if ( !System.IO.File.Exists( Context.Server.MapPath( url ) ) )
Context.RewritePath( ROOT_DOCUMENT );
}
There are a few other caveats here:
Each platform handles rewrites differently. In the case of IIS, the URL is rewritten, and the remainder of the route is lost. i.e. for localhost:1235/register, you will actually load localhost:1235/, and Angular will never receive the /register route. Essentially, all routes not known by IIS will return your app entry point.
You can set up multiple entry points to your app, but any in-memory references to variables, settings, etc. will be lost when moving from one entry point to another.
Given these two points, you should always consider any external URLs to be a fresh copy of your Angular application, and handle them accordingly.
Here's another way to reroute 404's to your SPA:
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404" subStatusCode="-1"/>
<error statusCode="404" prefixLanguageFilePath="" path="/spa-location.html" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>