Static assets

Contents

Add static assets like images, CSS and JavaScript to your website.

Serving a CSS file

So far, the site is primarily made up of dynamic HTML and images. Let’s have a look at how to handle static assets. In our case, we’ll use a CSS file.

Enonic supports an "out-of-the-box" solution for serving static assets through the Asset service. Simply by placing files in the src/resources/assets folder you make them instantly available at a specific URL.

Your application already includes a sample CSS file: src/main/resources/assets/mvp.css. Let’s put it to work by referencing it from the page component:

Update your page component view with the following content:

src/main/resources/site/pages/hello/hello.html
<!DOCTYPE html>
<html color-mode="user">
  <head>
    <title data-th-text="${displayName}">Sample title</title>
    <link rel="stylesheet" data-th-href="${portal.assetUrl({'_path=mvp.css'})}" href="../assets/mvp.css" type="text/css" media="all"/>
  </head>
  <body>
    <header>
      <h1 data-th-text="${displayName} + ' - with regions'">Sample header</h1>
    </header>
    <main data-portal-region="main">
        <div data-th-if="${mainRegion}" data-th-each="component : ${mainRegion.components}" data-th-remove="tag">
            <div data-portal-component="${component.path}" data-th-remove="tag"></div>
        </div>
    </main>
    <footer>My first site - powered by Enonic</footer>
  </body>
</html>

There are only two changes in this file: one line that includes the CSS file, and a small attribute on the html element to activate dark mode support (if it’s switched on in your system).

If you’re using dark mode, the result will look something like this:

Countries page in dark mode after adding the css

Perfect cache

When running Enonic XP in production, every asset will include cache headers that let proxies/CDNs and browsers cache it - without ever having to download it again.

How does it work?

The generated asset URL will contain a prefix based on a signature of the files in the app. The signature will change with every new version of the app. The portal.assetUrl() function uses this signature when generating the URL, ensuring that clients always get the most recent version.

When running in dev mode (like you do now), cache headers are deactivated

Static Assets library

If you’re using advanced build-tools assets may be chunked and served incrementally. Changing the URL to all assets for every re-deploy is no longer optimal. You can take full control over asset serving by adding Static Assets library (or lib-static) to your app.

The library is pre-bundled with this application and processes static assets located in the src/main/resources/static folder. In order to enable immutable urls, a content hash is added to the file names at compile time. The content hash is generated from the file content (and location) and changes only if the content (or location) of the file has changed. This means that the browser can cache the asset "forever", and every time it encounters the url it can simply load the asset from the cache.

Let’s add one of the static assets (the Enonic logo) to the page:

Update your page component controller with the following code:

import type { Response } from '/index.d';

// @ts-expect-error no-types
import { render } from '/lib/thymeleaf';
import { getContent } from '/lib/xp/portal';
import { getStaticAssetUrl } from '../../static';

// Specify the view file to use
const VIEW = resolve('./hello.html');

// Handle the GET request
export function get(): Response {

	// Get the content that is using the page
	const content = getContent();

	// Prepare the model that will be passed to the view
	const model = {
		displayName: content.displayName,
		mainRegion: content.page.regions.main,
		staticCssUrl: getStaticAssetUrl('styles.css'),
	}

	// Prepare the response object
	const response: Response = {
		body: render(VIEW, model)
	};

	return response;
}

The main change in the controller is the addition of the staticCssUrl property that is passed to the template. Value for this property is generated via helper getAssetUrl function which accepts original file name of an asset as an argument and generates full url (with the hash) to the asset.

Now update your page component template with the following code:

src/main/resources/site/pages/hello/hello.html
<!DOCTYPE html>
<html color-mode="user">
  <head>
	<title data-th-text="${displayName}">Sample title</title>
	<link rel="stylesheet" data-th-href="${portal.assetUrl({'_path=mvp.css'})}" href="../assets/mvp.css" type="text/css" media="all"/>
	<link rel="stylesheet" data-th-href="${staticCssUrl}" href="static/styles.css" type="text/css" media="all"/>
  </head>
  <body>
    <header>
      <h1 class="enonic-logo" data-th-text="${displayName} + ' - with regions'">Sample header</h1>
    </header>
    <main data-portal-region="main">
        <div data-th-if="${mainRegion}" data-th-each="component : ${mainRegion.components}" data-th-remove="tag">
            <div data-portal-component="${component.path}" data-th-remove="tag"></div>
        </div>
    </main>
    <footer>My first site - powered by Enonic</footer>
  </body>
</html>

There are two changes in this template. First is addition of the <link> tag which references the static CSS file. Second is addition of the class="enonic-logo" on the <h1> tag which injects Enonic logo via provided CSS.

Page template with static CSS

Summary

You now know how to serve static assets.

In the next chapter you’ll learn about layouts. Layouts are essentially parts that also support regions.


Contents

Contents