Nested routing in Shiny/R app

,

I'm trying to handle nested path routing in my Shiny Server-hosted app, but am finding that requests any deeper than one past the root level are failing (due to improper mapping of the framework's assets to the correct filesystem location). Here's my reprex app.R:

library(shiny)
                                                                                                                                                                                 
initial_req <- NULL

ui <- function(req) {
  initial_req <<- req

  fluidPage(
    verbatimTextOutput("session")
  )
}


server <- function(input, output, session) {
  print(sprintf("starting session, token = %s", session$token))

  output[["session"]] <- renderPrint({
    initial_req |> as.list() |> str()
    session$request |> as.list() |> str()
  })
}

app <-
  shinyApp(
    ui = ui,
    server = server,
    uiPattern = ".*"
  )

app

And here's the shiny-server.conf:

run_as shiny;

server {
  listen 3838;

  location / {
    app_dir /srv/shiny-server;
    log_dir /var/log/shiny-server;
  }
}

Working requests:

  • http://localhost:3838
  • http://localhost:3838/foo

Non-working requests:

  • http://localhost:3838/foo/bar (and any others deeper)

I'm confused as to why /foo works by /foo/bar doesn't, as the location is set to / ... so either I'd expect both of those nested path requests to work or both to fail :-/

I'd like to accept arbitrary URL path depths in my app (i.e. handling routing within the app).

At least when I was messing with this a few years ago, the default Shiny HTML template used relative URLs for all of its external JS/CSS resources, e.g.

<script src="shiny-javascript-1.11.1/shiny.min.js"></script>
<link href="bootstrap-3.4.1/css/bootstrap.min.css" rel="stylesheet" />

These relative links work fine under /foo, but when accessed from /foo/bar, they'll go out to /foo/shiny-javascript-1.11.1/shiny.min.js when the real file is at /shiny-javascript-1.11.1/shiny.min.js.

The only way to work around this IIRC is to use your own HTML template:

  1. Add a <base> tag to force the relative links to root at / or wherever your app is hosted. base tags can break other things though, and I remember it broke Connect or shinyapps.io which using their own base tags. Shiny Server might be fine.
<!DOCTYPE html>
<!-- template.html -->
<html>
  <head>
    <base href="/">
    {{ headContent() }}
  </head>
  <body>
    {{ content }}
  </body>
</html>
  1. Duplicate Shiny's default <head> content into an HTML template with relative URLs manually rewritten into absolute URLs. This is the path I took, but you're on the hook for maintaining the head content which is internal stuff and will change (break your app) across Shiny versions.
<!DOCTYPE html>
<!-- template.html -->
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="/jquery-3.7.1/jquery.min.js"></script>
    <link href="/shiny-css-1.11.1/shiny.min.css" rel="stylesheet" />
    <link href="/shiny-busy-indicators-1.11.1/busy-indicators.css" rel="stylesheet" />
    <script src="/shiny-javascript-1.11.1/shiny.min.js"></script>
    <link href="/bootstrap-3.4.1/css/bootstrap.min.css" rel="stylesheet" />
    <link
      href="/bootstrap-3.4.1/accessibility/css/bootstrap-accessibility.min.css"
      rel="stylesheet"
    />
    <script src="/bootstrap-3.4.1/js/bootstrap.min.js"></script>
    <script src="/bootstrap-3.4.1/accessibility/js/bootstrap-accessibility.min.js"></script>
    <script src="/shared/shiny-autoreload.js"></script>
  </head>
  <body>
    {{ content }}
  </body>
</html>

Thanks, I did eventually figure this out and indeed needed to use the template method, as described here.

I think needing to use a template purely to force introduction of the <base> tag ahead of the framework tags/files is a bit clunky ... since the HTML spec for <base> defines at most one such tag per page and that it must precede any relative path references, I think if that tag is included in the UI-building wrappers that it should be hoisted to the top of the page.

I'll suggest this in a Shiny issue over on GitHub, thanks again for the tip!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.