6  Static Files

Web applications frequently need to deliver assets which do not change on every request: HTML pages, CSS stylesheets, JavaScript bundles, images, fonts, or other files stored on disk.

Ambiorix provides a straightforward way to expose such files via the app$static() method, without extra routing logic or additional handlers.

6.1 Serving a Directory

Expose a folder on disk and map it to a URL prefix:

app$static(path = "public", uri = "static")
  • path: <String>, <Required>. Path pointing to the directory on your filesystem.
  • uri: <String>, <Optional>. The URL prefix under which the folder becomes publicly accessible. If not specified, defaults to “www”.

With the example above, any file placed in public/ becomes available at:

/static/filename

For example:

File System Mapped URL
public/styles.css /static/styles.css
public/main.js /static/main.js
public/images/logo.png /static/images/logo.png

6.2 Multiple Static Directories

If you need to serve multiple directories as static content, simply call app$static() multiple times with different path & uri values.

For example:

app$static(path = "public", uri = "static")
app$static(path = "assets", uri = "assets")

6.3 Example App

Say you have lots of photos of your cats. Let’s build a photo gallery.

6.3.1 Setup

  1. Create a new directory for the project and switch to it:

    mkdir photo-gallery
    cd photo-gallery
  2. In the project directory, create a new directory called public/:

    mkdir public
  3. Create an images/ directory in public/.

    mkdir public/images
  4. Place the photos of your cats in the public/images/ dir.

  5. We’ll want some minimal styling for the images, so create the file styles.css in the public/ dir:

    touch public/styles.css
  6. Finally, create an index.R file in the root dir of the project:

    touch index.R

At this point, your file tree structure should look something like this:

.
├── index.R
└── public
    ├── images
    │   ├── 0.jpeg
    │   ├── 1.jpeg
    │   ├── 2.jpeg
    │   ├── 3.webp
    │   ├── 4.webp
    │   ├── 5.avif
    │   ├── 6.jpeg
    │   ├── 7.jpg
    │   ├── 8.jpeg
    │   └── 9.webp
    └── styles.css

6.3.2 index.R

  1. Load {ambiorix} and {htmltools}, instantiate a new Ambiorix application, and set the static directory:

    library(ambiorix)
    library(htmltools)
    
    app <- Ambiorix$new()
    
    app$static(path = "public", uri = "static")
  2. Define a generic UI Page():

    #' Generic UI page
    #'
    #' @param ... [htmltools::tags] Passed to the HTML
    #' document body.
    #'
    #' @return [htmltools::tagList]
    #'
    #' @export
    Page <- function(...) {
      tagList(
        HTML("<!doctype html>"),
        tags$html(
          lang = "en",
          tags$head(
            tags$title("Photo Gallery"),
            tags$link(
              rel = "stylesheet",
              href = "/static/styles.css"
            )
          ),
          tags$body(
            ...
          )
        )
      )
    }
  3. Define a handler for GET requests at / (the “home page”):

    #' Handle GET at '/'
    #'
    #' @export
    home_get <- function(req, res) {
      img_names <- list.files(path = file.path("public", "images"))
      img_src <- paste0("/static/images/", img_names)
      img_tags <- lapply(
        X = img_src,
        FUN = \(src) {
          tags$img(
            src = src,
            loading = "lazy"
          )
        }
      )
      img_content <- tags$div(
        class = "img-content",
        img_tags
      )
    
      html <- Page(img_content)
    
      res$send(html)
    }
  4. Attach the handler and start the app on port 8000:

    app$get("/", home_get)
    
    app$start(port = 8000L)

Up to this point, you should be able to run the app and see the photo gallery.

It looks a bit ugly and unstructured, let’s add a wee bit of styling to it.

6.3.3 public/styles.css

  1. For the document body:

    • Remove all default outer margin applied by browsers.
    • Set an all-round padding inside the body.
    • Use a dark background
    body {
      margin: 0;
      padding: 40px;
      background: #0f1117;
    }
  2. For the container of the images:

    • Turn the container into a CSS Grid layout, enabling structured rows/columns.
    • Create 3 equal-width columns. 1fr means 1 fraction of the available space.
    • Insert a gap between all grid items (both rows & columns).
    .img-content {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 5px;
    }
  3. Finally, for the images in that container:

    • Make each image fill the full width of its grid column.
    • Set a fixed height.
    • Crop the image as needed to maintain aspect ratio while filling the width/height box.
    • Slightly round the image corners.
    .img-content img {
      width: 100%;
      height: 300px;
      object-fit: cover;
      border-radius: 5px;
    }

6.3.4 Results

When you hard-refresh the browser, the photo gallery will be more palatable to the eye.