Page Metadata
Have you ever pasted a link to a page into some program which, after a brief fetch, ended up surfacing information about that page in an infobox? This is possible because every HTML page supports having a <head>
block, which is where you can specify metadata about it.
However, Kobweb generates the HTML for your site automatically! So how can you specify this metadata?
There are two ways: via the build script (for metadata that should be present on every page), and through code using the Kotlin/JS APIs. Most projects will use both approaches, so we'll explore both in this article.
Site-wide metadata
For any build script that applies the Kobweb Application or Library Gradle plugins, you are given access to the index
block, which will either be found under the kobweb.app
or kobweb.library
blocks, respectively.
The index
block then contains a head
property, which is where you can specify the metadata for your page, using the Kotlinx.html DSL.
Let's demonstrate this with a concrete example. Say you ultimately want to generate an HTML page with a <head>
block that looks like the following:
In your build script:
For build scripts that apply the Kobweb application plugin, Kobweb provides the description
property as a convenience, but it is ultimately just shorthand for adding a <meta>
tag with the appropriate attributes -- here, <meta name="description" content="..." />
.
It is provided because it is so commonly used and every site should probably have one. We also want to encourage users setting their description inside the application module and not in some library module, as users who consume it might not expect that.
In a similar fashion, Kobweb application build scripts also will add an icon
link into the <head>
block by default as well, which you can control using the faviconPath
property (which defaults to "/favicon.ico"
).
Our <head>
block is most of the way there, but we're still missing the title! This is because the title is actually specified inside the site's .kobweb/conf.yaml
file. Here, it should look like:
And with that, all Kobweb pages for this site will now include the <head>
block that we wanted.
URL interception
Many popular web services are provided as bundles of resources (scripts, stylesheets, and data) which, together, provide functionality and/or style. These often get hosted on content delivery networks (commonly abbreviated as CDNs). Installation instructions often recommend using them as an easy option -- just add a script tag and stylesheet link to your page's <head>
block, and you're good to go!
Unfortunately, this easy solution may result in a site not being GDPR-compliant. In short, this is because requests made to the CDN involve sharing the user's IP address as part of the transaction, and GDPR does not allow sharing personal data without user consent with servers that don't follow European data protection laws.
Further discussion about GDPR is far outside the scope of this documentation, but if you're interested, you may want to review links like https://en.wikipedia.org/wiki/Content_delivery_network#Security_and_privacy and find blog posts discussing the topic in more detail.
A common way to avoid issues with GDPR compliance is by self-hosting resources yourself. Kobweb aims to make this as easy as possible through URL interception, which you can configure in the index
block.
Automatic self-hosting
The easiest approach is to just let Kobweb handle self-hosting itself, by opting into it:
Let's use a real-example to demonstrate how this works. Suppose you want to use the popular glMatrix library for adding OpenGL support to your site (as kobweb create examples/opengl
does). After searching, you find it is hosted on Cloudflare:
If you navigate to your site and open up the network tab in your browser's dev tools, you'll see that it makes a request to the expected URL.
However, if you opt in for self-hosting:
and re-run your site, then you'll see that a copy of gl-matrix-min.js
was downloaded locally at build time. If you check the network tab now, you'll see that the file is hosted on your own server, at a URL like https://yoursite.com/_kobweb/self-host/cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.2/gl-matrix-min.js
.
A really nice feature of self-hosting is that it will inspect any target resource that is a CSS file (i.e. a stylesheet) and recursively download any additional resources it references. Otherwise, if you had only downloaded such a stylesheet yourself, you might think you were done without realizing that you were still breaking GDPR compliance as additional requests were being made to fetch additional resources.
In general, CDN links offer quite a few advantages, so you shouldn't replace them just because it is easy to do so -- you'll increase your bandwidth bill, while at the same time likely serve those files much slower to your users than a CDN would have. You should carefully consider your situation and the tradeoffs before committing to self-hosting.
Manually intercepting URLs
If you want more control over which URLs get intercepted, you can specify your own replace
rules in the URL interception block.
Let's say instead of you specifying the glMatrix dependency yourself, it was done by a third-party Kobweb library. When you go to build your site, Kobweb's Gradle output will inform you that the Cloudflare gl-matrix-min.js
URL will be added to your final page (or you might notice this after opening your page and inspecting its <head>
block).
While you could just use enableSelfHosting()
here, let's show how you could do a manual interception:
At this point, you'd be on the hook for downloading the script file yourself and placing it in the correct location (here, site/src/jsMain/resources/assets/scripts/gl-matrix-min.js
). But after that, you'd be self-hosting again!
Checking the network tab again, you should see that gl-matrix-min.js
is now being served from your domain and not from a CDN.
Page-specific metadata
Often, you will want to set metadata dynamically on a per-page basis.
For example, maybe you don't want the title of your site to be shown for every page, but instead you want to override it with the page's title (especially as this is the name that appears in the browser tab). And while we're at it, let's update the description as well, since the two should definitely be kept in sync.
To accomplish this, we'll use the JavaScript standard library, which provides functionality for reaching into the DOM and modifying it. Using Kotlin/JS, you can write:
The above code is actually making a strong assumption that a <meta>
tag with the name description
already exists in your page's <head>
block (notice the !!
operator after the querySelector
call).
However, if you're potentially working with a head element that might not always exist, or if you want to write the above code a bit more defensively, you can use a "query or create" pattern:
A very natural place to put code like this is inside a layout composable, often inside a LaunchedEffect
:
You can find us using this pattern in the Kobweb site itself.
And that's really all there is to it. You may wish to familiarize yourself with the querySelector
method and the Document
interface exposed by Kotlin/JS.