Runtime Page Optimizer

October 12, 2008 6:17 pm | 33 Comments

The Runtime Page Optimizer (RPO) is an exciting product from Aptimize. RPO runs on a web server applying performance optimizations to pages at runtime, just before the page is sent to the browser. RPO automatically implements many of the best practices from my book and YSlow, so the guys from Aptimize contacted me and showed me an early version. Here are the performance improvements RPO delivers:

  • minifies, combines and compresses JavaScript files
  • minifies, combines and compresses stylesheets
  • combines images into CSS sprites
  • inlines images inside the stylesheet
  • turns on gzip compression
  • sets far future Expires headers
  • loads scripts asynchronously

RPO reduces the number of HTTP requests as well as reducing the amount of data that is transmitted, resulting in a page that loads faster. In doing this the big question is, how much overhead does this add at runtime? RPO caches the resources it generates (combined scripts, combined stylesheets, sprites). The primary realtime cost is changing the HTML markup. Static pages, after they are massaged, are also cached. Dynamic HTML can be optimized without a significant slowdown, much less than what’s gained by adding these performance benefits.

RPO is available for SharePoint, ASP.NET and DotNetNuke sites, and they’re working on a version for Apache. This is an exciting step in the world of web performance, shifting the burden of optimization from the web developer to making pages fast by default. They’ve just released RPO, but they’re deploying to at least one global top 1000 web site, so stats should be available soon. I can’t wait to see!

33 Responses to Runtime Page Optimizer

  1. What’s the timeline on the Apache release? Do you know if it’s for Apache 1.3 or 2.X?

  2. How does it handle exceptions? For example, what if some scripts shouldn’t be cached?

    What if scripts are combined in different ways on different pages? It sounds like this could potentially increase the bandwidth on pages through non-intelligent optimization.

  3. @Steve – You can signup for the Linux Apache beta now ( http://www.getrpo.com/Product/Apache/ ). I’ll ask the Aptimize guys to comment on dates for Apache.

    @Joe – There are a ton of config options to control what RPO does. I saw early documentation. I’ll post a URL here when I find it.

  4. Hmm, tuning server setting by hand vs. optimizing with a machine on every page delivered? I’d probably choose the first.

    Once you get to the sub-500ms delivery times you tend to avoid stuff like this…

    But it sounds like a good first remedy when you get to maintain an awful site.

  5. Jan: “But it sounds like a good first remedy when you get to maintain an awful site” — well that sounds like most ASP.NET sites, so this seems good for that :)

  6. Apache: Right now, We’re developing RPO Apache. This is a a native Apache module – not a port of the .NET component. ETA for the first beta version is several weeks, and we’re looking for people who want to try it on live sites. Email me if you’re interested.

    Ed Robinson
    CEO
    Aptimize Ltd
    ed.robinson@aptimize.net

  7. Joe – You can control the way that resources are optimized at a granular level. Full documentation of the RPO configuration settings can be found here: http://www.getrpo.com/support.

  8. isn’t this similar to smartoptomizer (http://farhadi.ir/works/smartoptimizer), a php library that generates minified, compressed files (js, css, etc) and then caches them for future use? I’ve been using it and Yslow shows significant improvements.

  9. I think the best way is semi-automatized solution, so web developers can choose what should be compressed and how. Because right now there are a lot of nuances with IE/Safari/etc.

    Also I think CSS Sprites are out of data (and correct automatized solution is too hard to be implemented). mhtml+data:URI will be the winner. You can see an example on http://webo.name/

  10. This seems to trade engineering time for some up front CPU time. And the former is generally much more expensive.

  11. One advantage of doing the optimization at runtime is that features are turned off for browsers that don’t support them – eg: IE6+ IE7 don’t support CSS inlining.
    You can also turn off the RPO programmatically using the ?RPO=off querystring parameter. Try this:
    RPO ON: http://www.getrpo.com
    RPO OFF: http://www.getrpo.com/?rpo=off

  12. How does the product handle under heavy load?

  13. Scales well under heavy load (and load balanced environments). The combination is done at server start up and cached. The only processing during runtime is lightweight parsing of the HTML.

  14. It seems promising, but why don’t they use it in their own site???
    http://www.getrpo.com
    7 js files
    4 css files
    31 css Imges

    I always have a hard time understanding this… That’s like sites promoting ajax frameworks but their site doesn’t even use ajax.
    Your sites should be the first place where you show off what you are “selling” to others.
    Why do people respect Steve Souders in performance related issues?
    Because he lead by example.
    Look at what he even done to some Yahoo sites, or even look at this stevesouders.com, blazing fast.

    Do you know what I mean?

  15. Just read the Ed Robinson indication about being possible to turn on or off the optimization. What seems to indicate that their site are using it.

    But strangely, my YSlow on their site showed the above statistics.

    Any bug or have you guys turned it off?

  16. Wow seems like a great product. I love how it just works even when using things that already do script combining such as script manager and telerik’s stylesheet manager.

  17. Yes the RPO is on our own site. The YSlow score for our homepage is 92 (please verify this).

    For Firefox 3, the page loads in 9 requests.

    If you’re seeing something different, here is why: The RPO detects the user agent string and turns off optimizations that aren’t supported for your browser. RPO supports the most common browsers: FF2,3; IE6,7,8; Chrome; Safari 3; Opera 9.

    Other browsers receive an unoptimized page. This means layout and functionality is preserved always. If your browser has a modified user agent string or using a tool with a different user agent, you can force the RPO on with this querystring http://www.getrpo.com/?rpo=on

    Ed Robinson

  18. Well, YSlow is not available for all browsers, so it is easy to guess the browser I’m using.

    And no, I’m not using any UA switcher.
    I’m using FF3 (v3.0.3).

    And actually your score is 72 and the number of files served are the ones indicated in a previous post.

    Maybe you should check this out.
    With the querystring it did worked ok!

  19. @sofia: Thanks for the pointer to smartoptimizer. I’ll mention it in the 2nd edition of my book.

    @Mr. Speed: (love the name) I also get a 92 in YSlow. Every rule is an “A” except rule 2 (“F”). Reply with your rule scores, and we can figure this out.

  20. Hmzz.. Im getting an 88 for the RPO site:

    Performance Grade: B (88) Expand All Collapse All
    A 1. Make fewer HTTP requests
    F 2. Use a CDN
    Using these CDN hostnames from your preferences: media.hotels.nl
    These components are not on a CDN:

    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/wqjqxGPF8StlqsFc-gq-NA
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:06:26 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Encoding: gzip
    Content-Length: 27866
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:06:26 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: text/javascript
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/2MVVB8kFaNvhbiV9qYDNEg
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:06:26 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Encoding: gzip
    Content-Length: 475
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:06:26 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: text/css
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/5iTI-AiY-gsULpMwbRdFaw
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:06:27 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Encoding: gzip
    Content-Length: 12429
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:06:27 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: text/css
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/memK7aNxuZwfe8zE2XSTfQ
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:06:27 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 34786
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:06:27 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/jpeg
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/QZLKZEKftWjsjeJV8LC0eA
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:06:27 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 8317
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:06:27 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/gif
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/2P8K$EdTvm8vKPxzvgBzgg
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:06:27 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 25712
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:06:27 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/jpeg
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/$HWC0H3TlHR2KHrQNahAtw
    *
    ParamsHeadersPost
    Response Headers
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/1×1
    *
    ParamsHeadersPost
    Response Headers
    Loading…

    C 3. Add an Expires header
    These components do not have a far future Expires header:

    * [HTTP headers] (no expires) http://www.getrpo.com/rpo.axd/1×1
    *
    ParamsHeadersPost
    Response Headers
    Loading…
    * [HTTP headers] (no expires) http://www.getrpo.com/rpo.axd/7/$HWC0H3TlHR2KHrQNahAtw
    *
    ParamsHeadersPost
    Response Headers
    Loading…

    A 4. Gzip components
    A 5. Put CSS at the top
    A 6. Put JS at the bottom
    A 7. Avoid CSS expressions
    n/a 8. Make JS and CSS external
    Only consider this if your property is a common user home page.
    A 9. Reduce DNS lookups
    A 10. Minify JS
    A 11. Avoid redirects
    A 12. Remove duplicate scripts
    A 13. Configure ETags

    Also, using FF3

  21. Oh thats great. I refresh and check aggain…
    And they get a 92..

    Performance Grade: A (92) Expand All Collapse All
    A 1. Make fewer HTTP requests
    F 2. Use a CDN
    Using these CDN hostnames from your preferences: media.hotels.nl
    These components are not on a CDN:

    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/wqjqxGPF8StlqsFc-gq-NA
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:12 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Encoding: gzip
    Content-Length: 27866
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:12 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: text/javascript
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/2MVVB8kFaNvhbiV9qYDNEg
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:12 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Encoding: gzip
    Content-Length: 475
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:12 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: text/css
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/5iTI-AiY-gsULpMwbRdFaw
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:12 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Encoding: gzip
    Content-Length: 12429
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:12 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: text/css
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/memK7aNxuZwfe8zE2XSTfQ
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:12 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 34786
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:12 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/jpeg
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/QZLKZEKftWjsjeJV8LC0eA
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:13 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 8317
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:13 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/gif
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/2P8K$EdTvm8vKPxzvgBzgg
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:13 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 25712
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:13 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/jpeg
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/7/$HWC0H3TlHR2KHrQNahAtw
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:13 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 104670
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:13 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/jpeg
    Loading…
    * [HTTP headers] http://www.getrpo.com/rpo.axd/1×1
    *
    ParamsHeadersPost
    Response Headers
    Date: Sat, 18 Oct 2008 11:08:14 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Content-Length: 44
    Cache-Control: private, max-age=31536000
    Expires: Sun, 18 Oct 2009 11:08:14 GMT
    Last-Modified: Tue, 01 Jan 2008 18:00:01 GMT
    Content-Type: image/gif
    Loading…

    A 3. Add an Expires header
    A 4. Gzip components
    A 5. Put CSS at the top
    A 6. Put JS at the bottom
    A 7. Avoid CSS expressions
    n/a 8. Make JS and CSS external
    Only consider this if your property is a common user home page.
    A 9. Reduce DNS lookups
    A 10. Minify JS
    A 11. Avoid redirects
    A 12. Remove duplicate scripts
    A 13. Configure ETags

    Something weird is going on.. :p

  22. I think almost every page on getrpo.com gives 92 YSlow score (A for everything except CDN). For most sites (even SharePoint) the RPO lifts YSlow by 20-40 points.

    If you’re not seeing this, I’m guessing there is something up with the browser’s user agent. Email me at ed.robinson@aptimize.net and I can help.

    Ed

  23. Hi Steve,

    Here is the info. Hope it helps!
    Initially my UA had some additional info added in the end (because of FirePHP), but I have disabled all plugins that change the UA, so my UA is now :
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3

    But even with other UA variants, like
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3 FirePHP/0.2.b.4

    The RPO should work, don’t you think?

    The YSlow info I get is :
    YSlow for Firebug
    ————————————————————————
    URL:http://www.getrpo.com/
    Performance
    *Performance Grade: C (73)*

    F 1. Make fewer HTTP requests

    This page has 7 external JavaScript files.

    This page has 4 external StyleSheets.

    This page has 31 CSS background images.

    F 2. Use a CDN
    These components are not on a CDN:
    * http://www.getrpo.com/Content/Scripts/prototype.js
    * http://www.getrpo.com/Content/Scripts/rpofunctions.js
    * http://www.getrpo.com/Content/Scripts/sifrInitializer.js
    * http://www.getrpo.com/Content/Scripts/sifr.lite.js
    * http://www.getrpo.com/Content/Scripts/viewport.js
    * http://www.getrpo.com/Content/Scripts/prototype.js
    * http://www.google-analytics.com/ga.js
    * http://www.getrpo.com/Content/Styles/Home.css
    * http://www.getrpo.com/Content/Styles/Site.css
    * http://www.getrpo.com/Content/Styles/Print.css
    * http://www.getrpo.com/Content/Styles/Login.css
    * http://www.getrpo.com/Content/Images/Site/bg.gif
    * http://www.getrpo.com/Content/Images/Login/login-bottom.gif
    * http://www.getrpo.com/Content/Images/Login/logindlg.jpg
    * http://www.getrpo.com/Content/Images/Login/login.gif
    * http://www.getrpo.com/Content/Images/Login/existing-tab-active.jpg
    * http://www.getrpo.com/Content/Images/Login/new-tab.jpg
    * http://www.getrpo.com/Content/Images/Buttons/login.gif
    * http://www.getrpo.com/Content/Images/Buttons/cancel.gif
    * http://www.getrpo.com/Content/Images/Login/sign-up.jpg
    * http://www.getrpo.com/Content/Images/Site/header.jpg
    * http://www.getrpo.com/Content/Images/Site/logo.jpg
    * http://www.getrpo.com/Content/Images/Site/Nav/login.jpg
    * http://www.getrpo.com/Content/Images/Site/Nav/product.gif
    * http://www.getrpo.com/Content/Images/Site/Nav/downloads.gif
    * http://www.getrpo.com/Content/Images/Site/Nav/buy.gif
    * http://www.getrpo.com/Content/Images/Site/Nav/blog.gif
    * http://www.getrpo.com/Content/Images/Site/Nav/support.gif
    * http://www.getrpo.com/Content/Images/Site/Nav/about-us.gif
    * http://www.getrpo.com/Content/Images/Home/banner-dropshadow-left.jpg
    * http://www.getrpo.com/Content/Images/Home/banner-dropshadow-bottom.jpg
    * http://www.getrpo.com/Content/Images/Home/banner.jpg
    * http://www.getrpo.com/Content/Images/Home/try-it-for-free.jpg
    * http://www.getrpo.com/Content/Images/Home/panel-right.gif
    * http://www.getrpo.com/Content/Images/Home/panel-left.gif
    * http://www.getrpo.com/Content/Images/Home/report.jpg
    * http://www.getrpo.com/Content/Images/Home/how-fast-is-my-web-site.jpg
    * http://www.getrpo.com/Content/Images/Home/derek.jpg
    * http://www.getrpo.com/Content/Images/Home/want-to-talk-first.jpg
    * http://www.getrpo.com/Content/Images/Home/results.jpg
    * http://www.getrpo.com/Content/Images/Home/proven-results.jpg
    * http://www.getrpo.com/Content/Images/Site/footer.jpg
    * http://www.getrpo.com/Content/Images/blank.gif
    * http://www.getrpo.com/Content/Images/Buttons/calculator.jpg
    A 3. Add an Expires header
    A 4. Gzip components
    F 5. Put CSS at the top
    4 external stylesheets were found outside the document HEAD.
    * http://www.getrpo.com/Content/Styles/Home.css
    * http://www.getrpo.com/Content/Styles/Site.css
    * http://www.getrpo.com/Content/Styles/Print.css
    * http://www.getrpo.com/Content/Styles/Login.css
    A 6. Put JS at the bottom
    A 7. Avoid CSS expressions
    n/a 8. Make JS and CSS external
    Only consider this if your property is a common user home page.
    A 9. Reduce DNS lookups
    A 10. Minify JS
    A 11. Avoid redirects
    B 12. Remove duplicate scripts
    This page has 1 redundant JS components:
    1 http://www.getrpo.com/Content/Scripts/prototype.js
    A 13. Configure ETags

  24. Definitely looks like RPO is off for you. Ed from RPO – can you comment? Is it the User-Agent not being recognized?

  25. If the RPO doesn’t recognize the user agent, the website is returned unoptimized.

    I’m guessing this is happening here. Mr Speed: if you email me offline I can help diagnose what’s going on.

  26. .axd file sets up the content expiration date for a period of 1 year, Is this a default seeting or is it customizable?. pls advise.

  27. Basheer: Yes, the expiration date is configurable.
    Mr Speedy: Your browser should be detected correctly now.

  28. We have been trialling this for a while, primarily driven by a colleague, on our Microsoft web properties (internal and external sites). Results were OK, but weren’t enough for me to pull together a business case.

    Have been waiting months for the Apache release (see comment “due in several weeks” from Oct last year), so delay is not inspiring confidence.

    Would suggest looking elsewhere.

  29. Hi Angus,
    thanks for trying Aptimize RPO – let us help you get the speed improvement you need to justify the business case. You can email me ed.robinson@aptimize.net – sometimes we need to help with configuration. Most people are amazed with the performance improvement which is where we can help you get to.

    You’re right Apache RPO is overdue. Keep an eye on our blog http://www.getrpo.com/blog we’ll be announcing it in the next couple of days.

    Ed

  30. Thanks, Ed. Talking it over with the team this week we don’t believe the investment is warranted. With some minor optimisation (albeit manual) combined with the faster browsers out there, we think this will suffice for our needs – and likely many others too.

  31. I disagree – I’ve been part of the beta testing group since December and have been using it on our site. Sure, optimizing by hand works, but for out-of-the-box products like Invision Power Board I’d rather spend time moderating my forum than hacking away at them to make them faster.

    Google Analytics showed a 40% (yes, forty percent) increase of page views of our main site and has reduced the data transfer on our forums by just over half. Whatever it’s doing is working for us and we eagerly await the final version after the beta testing is complete.

  32. Erik’s results with the Apache RPO are now posted on our blog:
    http://www.getrpo.com/Blog/Archive/2009/3/InfamousRO-Apache-Results

    Ed Robinson
    Chief Executive Officer
    Aptimize Ltd

  33. I just finished checking out the tool and I feel that the more transparent the performance solutions the more popular and widespread they can become. I’m also happy to see more attention being paid to what is in my personal experience a large number of “heavy” .net applications on the web.