Table of contents
In my experience, static files in APEX are often abused for the sake of expediency. I often see people doing the most straightforward thing rather than the right thing. This can lead to a poor user experience, unnecessary load on your ORDS server, and maintenance headaches.
Before I start, I want to level-set what static files are in relation to APEX. As the name suggests, static files cannot be changed at runtime. Static files can be referenced and loaded by APEX and the content used by your APEX applications. Examples of static files include:
- CSS Style Sheets
- Images and Logos]
- APEX Customized Theme Styles
- APEX Plugin JS and CSS
- APEX Office Print Templates
For static files to be loaded by APEX, you need to specify where they are stored. APEX provides a lot of flexibility in storing and referencing static files. This post will describe the three most common places to store static files and the best practices for each. I will also discuss methods for compressing and versioning static files. Finally, I’ll share the results of the tests I ran with timings for loading static files from the three central static file locations.
This post assumes you are at least familiar with static files and have used them in your APEX applications.
The diagram below shows a typical configuration for APEX and static files.
The diagram below shows the ideal state for APEX and static files.
Options for Storing APEX Static Files
Static Application/Workspace Files (in the APEX application definition), a Content Delivery Network (CDN), and the ORDS File Server are the three most common places to store static files. In this section, I will describe each approach, list their benefits and drawbacks and finally describe when to use each option.
Static Application or Workspace Files
As the name suggests, Static Application Files can only be referenced within the Application in which they are stored. Static Workspace Files can be referenced from any applications within the workspace. Other than that distinction, they behave the same way, so I will only reference Static Application Files in the future.
This screenshot shows several Static Application Files, which we will be referencing throughout the post.
This is the most straightforward and obvious location to store static files. There are a few things you should consider when using Static Application Files.
- When your application references a file in Static Application Files, ORDS uses a database connection to get the file. Once retrieved, the file is cached by your browser so that future requests for the file are served from the browser cache. Whenever the browser cache expires, or you change the file, a database connection is used to get the file again. This is not an issue for one or two small files and a handful of users. If you have many files and users, this can cause an unnecessary burden on your ORDS server and database connection pool.
- Because these static files are stored in the Application Definition, the application export also contains the static files. This increases the export file size, slowing deployment times and making your application export files unwieldy.
#MIN# Substitution String The #MIN# substitution is empty when running your application in debug mode and is set to '.min' when not in debug mode. This is helpful when debugging issues with JS and CSS static files because it allows you to see the uncompressed file. When you are not debugging, the minified version of the file is used, and static file load times are improved.
Example Page Level JS static file reference using #MIN#
Benefits of Static Application Files
- Files are right there and easy to change directly from APEX Builder
- Files are packaged and deployed with your application
- APEX handles file versioning
- Fetching uncached files adds a small load to your ORDS server
- A database connection is used when fetching uncached files
- Application export files can become large and unwieldy
- You cannot reference static files from APEX Applications in different workspaces
When to Use
- You do not have access to a CDN or the ORDS file server
- You have less than ten static files, and most of them are less than 20k in size each
- The static files do not need to be referenced outside of the Application or Workspace
- Your application has less than five concurrent users
- The application users are located in the same country as the ORDS server & database
Content Delivery Network (CDN)
A CDN (content delivery network), also called a content distribution network, is a group of geographically distributed and interconnected servers. They provide cached internet content from a network location closest to a user to speed up its delivery. A CDN is the ultimate location for your static files. There are several CDN providers, including Cloudflare and CloudFront, from AWS.
I have used AWS CloudFront several times. CloudFront allows you to identify a source for your static files (typically an AWS S3 bucket). You then use the CloudFront provided URL to reference your static files in APEX. CloudFront takes care of syncing these files from your S3 bucket to edge servers worldwide.
You need to consider caching and file versioning when using any CDN. With CloudFront, if you change a file in the source S3 bucket, it can take up to 24 hours for that file to be pushed out to all edge servers. This means your users will be using an old file version until the cache expires (not good). CloudFront allows you to invalidate the cache and force CloudFront to fetch the latest file copy from S3. This comes with a cost if you do more than 1,000 invalidations monthly. A better option is to include version numbers in your file names (see the section on versioning below for details).
Benefits of Using a CDN
- Files are stored close to your users, reducing network time
- Highly optimized servers serve files
- CDNs offer file compression capabilities
- This is the fastest way to serve static files to your users
- Storing files in cheap object storage keeps files out of the App definition, which uses unnecessary database storage
- Uploading files and new versions of files to the source storage location (e.g., S3) can be cumbersome (especially during development)
- Caching and invalidating caches can be confusing
- You need to take care of versioning static files yourself
- Not an option if your IT department does not allow any outbound network connections (pretty unlikely given the number of Cloud solutions in play these days)
When to Use
- You have an IT department capable of supporting a CDN
- You have many static files or large static files
- The static files need to be referenced from other APEX and Non-APEX Applications (e.g., Mobile Apps)
- Your application has more than 20 concurrent users
- The application users are located in different countries to the ORDS server
ORDS File Server
Another static file option is storing them on your ORDS file server. Note: This is only an option if you manage the ORDS server and can place files there. This includes on-premise installs or Customer Managed ORDS on Oracle Cloud Infrastructure (OCI) Autonomous database. This is not an option if you use the OCI APEX Service.
- A database connection is not required to get files as they are served directly by the web server
- You can make use of advanced caching and compression functionality on the web server (e.g., NGINX, Apache)
- Storing files in cheaper file system storage keeps files out of the App definition, which uses unnecessary database storage
- Fetching uncached files does incur a small overhead on the ORDS server
- Your ORDS server may not necessarily be located close to your users
- You need to take care of versioning static files yourself
- Copying / FTPing files to the ORDS server can be cumbersome
- Your IT department will be responsible for backing up files on the ORDS server
When to Use
- Your IT department manages the ORDS server
- You have many static files or large static files
- The static files need to be referenced from other APEX Applications
- Your application has more than ten concurrent users
- Your IT department does not allow any outbound network connections
Reducing Static File Sizes
When dealing with static files, the smaller the file, the quicker it loads. This applies no matter where you store your static files. There are several ways you can reduce the size of your static files.
Image Compression and Format
There are desktop and on-line tools that allow you to compress jpg and png files without a significant loss in quality. It takes a few seconds to do and any reduction in file size improves static file load times. In the screen shot above, you can see the XX_Landscape.jpeg file went from 116kb to 91kb after compression.
You can get even better results by using the 'WebP' image format. WebP lossless images are 26% smaller in size compared to PNGs. WebP lossy images are 25-34% smaller than comparable JPEG images at equivalent quality.
In the screen shot above, you can see the XX_Landscape.jpeg file went from 116kb to 69kb after converting it to '.webp'.
Static File Load Performance
This section describes a test case I constructed to see what difference the location and size of static files makes.
I used the following test files:
- XX_Landscape.jpeg (116KB) uncompressed image file
- XX_Landscape.webp (69KB) XX_Landscape.jpeg converted to webp format
- XX_LoDash.js (112KB) The latest version of the LoDash JS library
- XX_LoDash.min.js (13KB) XX_LoDash.js minified by APEX after uploading to Static Application Files
Each file was loaded into Static Application Files of an APEX Application, an AWS S3 bucket behind an AWS CloudFront CDN and an ORDS file server. The ORDS file server is an Always Free 1GB OCI Linux VM and the APEX instance is an Always Free ATP instance (both running from Ashburn in NC). The tests were run from San Diego California which is about 2,500 miles from the OCI data center. The web server is NGINX, the version of ORDS is 21.4 and the version of APEX is 21.2.
I used the Siege load testing tool to fetch each file 10 times using a single thread.
Here is an example of the Siege scripts run for the js files. Each URL is accessed 10 times (-r10) with one thread (-c1).
-- APEX Static Files siege -c1 -r10 "https://www.example.com/ords/dev/r/cndemo/111/files/static/v13/XX_LoDash.js" siege -c1 -r10 "https://www.example.com/ords/dev/r/cndemo/111/files/static/v13/XX_LoDash.min.js" -- ORDS Server siege -c1 -r10 "https://www.example.com/XX_LoDash.js" siege -c1 -r10 "https://www.example.com/XX_LoDash.min.js" -- CDN siege -c1 -r10 "https://test.cloudfront.net/XX_LoDash.js" siege -c1 -r10 "https://test.cloudfront.net/XX_LoDash.min.js"
- Smaller files are always faster... Always!
- The CDN is always faster... Always!
- It appears that the CDN applies some additional compression (probably gzip) to text files as the total MB value is lower than the ORDS server or APEX Static files.
- I was surprised to see Static Files on the ORDS Server so closely matched with files coming from APEX Static Application Files. I suspect the low spec VM for the ORDS server (or my NGINX configuration) may have caused this
I have touched on static file versioning a couple of times in this post. If you store and reference static files anywhere but APEX Static Application or Workspace files, then you need to consider file versioning. Depending on where you serve your static files from, files can be cached by a CDN and by the browser. The only way to make sure the latest version of a static file reaches your end users (other than invalidating the cache) is to somehow change the name of the file. There are two approaches for this.
Version Number in File Name
The preferred approach is to use a version number in the file name e.g. XX_Landscape_v3.webp
When the browser sees that it doesn't have the file cached it will get the file from the source. Similarly with a CDN, if it sees it doesn't have a certain file name cached on its edge servers, it will fetch it from the source location and then cache it.
- As soon as you upload a file with a new name/version number and reference it, you can be sure you are getting the latest version.
- Versioning in the file name provides a way to serve different versions of files to different users/applications.
- Versioning in the file name simplifies rolling forward and back between file revisions.
Add Version Number in Query String
Another option is to add a version number to the Query String for the file. e.g. XX_Landscape.webp?v=4
Seeing a different query string makes the browser or CDN think it has never seen the file before and it fetches the file from the source (and then caches it).
If you are in the habit of updating the version of your APEX applications (see screen shot below), then you can reference this in your static file references using the substitution string #APP_VERSION#. e.g. XX_Landscape.webp?v=#APP_VERSION# Doing this guarantees a fresh copy of the file will be fetched after every Application Deployment.
APEX Product Static Files
If you think about it, the most frequently used static files (and the largest) are the ones used by APEX itself. This includes font APEX, the Universal theme, Maps, Oracle JET etc. etc. Since APEX 19.1 Oracle has provided a CDN to host the APEX product static files. See here for details.
You can configure specific applications to use the Oracle CDN by entering the CDN URL under 'Static File Prefix' in Application Shared Components: Note: When you set the CDN URL at the Application level, then APEX will also look for Shared Components > Static Application Files under this URL. In the screen shot above, the substitution string #APP_FILES# will get changed to static.oracle.com/cdn/apex/21.2.2. This means that if you want to reference custom JS, CSS or Image files, you cannot store them under Shared Components > Static Application Files. You will need to host custom static files in object storage, a file server or your own CDN and provide references to the complete URL for each file
You can set your entire instance to use the Oracle CDN by running the following from a database account with the APEX_ADMINISTRATOR_ROLE role:
begin apex_instance_admin.set_parameter( p_parameter => 'IMAGE_PREFIX', p_value => 'https://static.oracle.com/cdn/apex/21.1.2/' ); commit; end;
Note: When you set the CDN URL at the Instance level, then APEX will not look for 'Static Application Files' under this URL. This means you can add custom JS, CSS or Image files in 'Static Application Files'.
If you set the CDN at the instance level, then you do not need to host the APEX product static files on your ORDS server at all. I would suggest that unless you are restricted by outbound firewall rules then you should give your ORDS server a break and set your entire APEX instance to use the CDN. Not needing to worry about the APEX static files also makes APEX and ORDS upgrades much simpler.
CDN URL You can get the appropriate CDN URL for your version of APEX from the 'Known Issues' page where you download APEX.
During development some solutions for storing static files can be cumbersome. For CDN and ORDS server solutions you will be constantly copying files, changing version numbers, clearing caches etc.
For Static Application Files the experience was similar unless you used something like APEX Nitro or the FOS APEX Builder Extension. APEX 21.2 improved the editing and minifying experience for Static Application Files enormously. APEX 22.1 takes this a step further by introducing Session Overrides. This essentially allows you to reference alternate static files (hosted on your laptop) during development. This allows you to change files, test and iterate with no file copying involved.
Static files are an important part of APEX. Too often expediency causes us to put static files in the easiest location thinking we will come back to it later. I challenge everyone to take some time think about how your static files will be used and choose the right approach not the quickest. Finally, you should always minify JS and CSS and use the '.webp' image format where possible.
If you use some of the approaches above, you can reduce the load on your ORDS server(s) significantly. This allows ORDS do what it does best which is to serve APEX pages and host REST web services.