| Title: | Multipage 'Shiny' Apps |
|---|---|
| Description: | Build natively multi-page 'Shiny' applications, serving content on several endpoints instead of a single page. Pages are declared with their own user interface and server function and are dispatched by URL with 'routr', including parameterised routes. Redirections, request and response middleware, and helpers to read and set cookies are also provided. |
| Authors: | Colin Fay [aut, cre, cph] (ORCID: <https://orcid.org/0000-0001-7343-1846>) |
| Maintainer: | Colin Fay <contact@colinfay.me> |
| License: | MIT + file LICENSE |
| Version: | 1.0.0 |
| Built: | 2026-10-14 18:56:53 UTC |
| Source: | https://github.com/cran/brochure |
This function is to be used in place of shinyApp(). It takes a series of
page()s, each with its own url, UI and server function, and serves them
from a single Shiny application.
brochureApp( ..., onStart = NULL, options = list(), enableBookmarking = NULL, content_404 = "Not found", basepath = "", req_handlers = list(), res_handlers = list(), wrapped = shiny::tagList )brochureApp( ..., onStart = NULL, options = list(), enableBookmarking = NULL, content_404 = "Not found", basepath = "", req_handlers = list(), res_handlers = list(), wrapped = shiny::tagList )
... |
The |
onStart |
A function that will be called before the app is actually run.
This is only needed for |
options |
Named options that should be passed to the |
enableBookmarking |
Can be one of |
content_404 |
The content served when no |
basepath |
The path your app is served under by a reverse proxy. It is
removed from the incoming url, so that what is left matches the href of your
|
req_handlers |
a list of functions that can manipulate the |
res_handlers |
A list of functions that can manipulate the httpResponse
object before it is send to the browser. Each function must take a |
wrapped |
A function taking the UI of a page and returning the UI
actually served. This is how every page gets the same shell: |
Behind a reverse proxy, basepath tells the app where it is mounted: it is
removed from the incoming path, and prepended to the urls the app emits.
On Posit Connect the mount is picked up on its own, from the
RStudio-Connect-App-Base-URL header, and basepath is not needed. That
header is not part of any published contract, so set basepath if you want
the behaviour pinned. Brochure also injects a small script registering the
handler server_redirect() talks to, which prefixes internal targets with
the mount.
A shiny.appobj
page() to declare a page, redirect() to answer an url with a
redirection, and vignette("deployment") to serve the app under a prefix.
library(shiny) app <- brochureApp( page( href = "/", ui = tagList(h1("Home")), server = function(input, output, session) {} ), page( href = "/contact", ui = tagList(h1("Contact")) ), redirect(from = "/index.html", to = "/") ) # Then run it as you would any Shiny app: if (interactive()) { shiny::runApp(app) }library(shiny) app <- brochureApp( page( href = "/", ui = tagList(h1("Home")), server = function(input, output, session) {} ), page( href = "/contact", ui = tagList(h1("Contact")) ), redirect(from = "/index.html", to = "/") ) # Then run it as you would any Shiny app: if (interactive()) { shiny::runApp(app) }
A page declared with a parameterised href (e.g. "/who/:id") exposes the
values matched in the URL through get_keys(). Call it with no argument
inside a page server, and pass it the request inside a page ui.
get_keys(x = shiny::getDefaultReactiveDomain())get_keys(x = shiny::getDefaultReactiveDomain())
x |
A shiny session, or the |
A named list of the values matched in the page href, or NULL
outside of a brochure page.
library(shiny) page( href = "/who/:id", ui = function(request) { h1(get_keys(request)$id) }, server = function(input, output, session) { print(get_keys()) } )library(shiny) page( href = "/who/:id", ui = function(request) { h1(get_keys(request)$id) }, server = function(input, output, session) { print(get_keys()) } )
Golem Hook function
golem_hook(path, package_name, ...)golem_hook(path, package_name, ...)
path |
Name of the folder to create the package in. This will also be used as the package name. |
package_name |
Package name to use. By default, |
... |
Arguments passed from |
Used for side effect
if (requireNamespace("golem") & interactive()) { golem::create_golem(file.path(tempdir(), "myapp"), project_hook = golem_hook) }if (requireNamespace("golem") & interactive()) { golem::create_golem(file.path(tempdir(), "myapp"), project_hook = golem_hook) }
Module template for golem
new_page(name, path, export, ...)new_page(name, path, export, ...)
name |
The name of the module. |
path |
The path to the R script where the module will be written.
Note that this path will not be set by the user but via
|
export |
Should the module be exported? Default is |
... |
Arguments to be passed to the |
Used for side effect
if (requireNamespace("golem") & interactive()) { golem::add_module(name = "home", module_template = brochure::new_page) }if (requireNamespace("golem") & interactive()) { golem::add_module(name = "home", module_template = brochure::new_page) }
A page is an url, a UI and a server function. Opening it starts a Shiny session of its own, separate from any other page of the app.
page( href, ui = tagList(), server = function(input, output, session) { }, method = "GET", req_handlers = list(), res_handlers = list() )page( href, ui = tagList(), server = function(input, output, session) { }, method = "GET", req_handlers = list(), res_handlers = list() )
href |
The endpoint to serve the UI on. It can carry parameters, as in
|
ui |
The UI definition of the app (for example, a call to
If bookmarking is enabled (see |
server |
A function with three parameters: |
method |
The HTTP method the page answers to. Defaults to |
req_handlers |
a list of functions that can manipulate the |
res_handlers |
A list of functions that can manipulate the httpResponse
object before it is send to the browser. Each function must take a |
Requests are matched against hrefs by the routr package, so an href is written the way routr writes a path:
"/contact" matches that path and nothing else.
":name" matches exactly one segment and captures it, so "/who/:id"
matches /who/colin but neither /who nor /who/colin/edit. Use several
of them if you need to: "/pair/:a/:b".
"*" matches whatever is left, so "/files/*" matches /files/a/b/c.
The captured value is named *1.
A trailing slash never matters: /contact and /contact/ are the same page.
Pages are tried in the order you passed them to brochureApp(), and the
first match wins. That holds for the session a page opens as well, which is
resolved on the path alone: the websocket handshake is a GET whatever
method the page answers on, so two pages sharing an href run the server of
the first one declared, whichever of them was served. That matters when two hrefs can match the same url:
declared as page("/who/me") then page("/who/:id"), a request for
/who/me gets the first; declared the other way round, the parameterised
page catches it and the static one is never reached. Put the specific ones
first.
See the routr documentation at https://routr.data-imaginist.com/ for the full path syntax.
A brochure_page object, to be passed to brochureApp().
get_keys() to read the parameters of an href, and
vignette("handlers") for req_handlers and res_handlers.
library(shiny) page( href = "/page2", ui = tagList( h1("This is my second page"), plotOutput("plotb") ) )library(shiny) page( href = "/page2", ui = tagList( h1("This is my second page"), plotOutput("plotb") ) )
get_cookies() returns the raw Cookie header of the request the session
was opened with, and parse_cookie_string() turns that header into a named
vector.
parse_cookie_string(cookie_string) get_cookies(session = shiny::getDefaultReactiveDomain())parse_cookie_string(cookie_string) get_cookies(session = shiny::getDefaultReactiveDomain())
cookie_string |
The cookie string to parse, as |
session |
The |
For get_cookies(), the Cookie header as a single string, or
NULL when the request carried none. For parse_cookie_string(), a named
character vector of the cookies it holds, or "" when given NULL. Index
it with single brackets: a visitor arriving without a cookie gives nothing
to index into, and [[ errors where [ returns NA.
set_cookie() to set one from a response handler, and
vignette("cookies") for carrying a session across pages.
parse_cookie_string("brochure_session=63422; brochure_cookie=3958")parse_cookie_string("brochure_session=63422; brochure_cookie=3958")
Answers an url with an HTTP redirection, before any Shiny code runs. To
redirect from inside a page server instead, see server_redirect().
redirect(from, to, code = 301, method = "GET")redirect(from, to, code = 301, method = "GET")
from |
the url to redirect from |
to |
the url to redirect to |
code |
redirecting http code, one of 301, 302, 303, 307 or 308 |
method |
The HTTP method the redirection answers to.
Defaults to |
A redirect object, to be passed to brochureApp().
server_redirect() to redirect from inside a page server.
redirect( from = "/index.html", to = "/" ) # 301 says the move is permanent, 302 that it is temporary redirect( from = "/old", to = "/new", code = 302 )redirect( from = "/index.html", to = "/" ) # 301 says the move is permanent, 302 that it is temporary redirect( from = "/old", to = "/new", code = 302 )
Do a server side redirection
server_redirect(to, session = shiny::getDefaultReactiveDomain())server_redirect(to, session = shiny::getDefaultReactiveDomain())
to |
the destination of the redirection: a path ( |
session |
shiny session object, default is |
Used for side effect
redirect() to answer an url with an HTTP redirection instead.
library(shiny) # `server_redirect()` is called from a page server, so it needs a session: page( href = "/login", ui = tagList(actionButton("go", "Take me home")), server = function(input, output, session) { observeEvent(input$go, { server_redirect("/") }) } )library(shiny) # `server_redirect()` is called from a page server, so it needs a session: page( href = "/login", ui = tagList(actionButton("go", "Take me home")), server = function(input, output, session) { observeEvent(input$go, { server_redirect("/") }) } )
Please read https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie for more information. Description of parameters is taken from this page.
set_cookie( res, name, value, expires = NULL, max_age = NULL, domain = NULL, path = NULL, secure = NULL, http_only = TRUE, same_site = "Lax" ) remove_cookie(res, name, path = NULL, domain = NULL)set_cookie( res, name, value, expires = NULL, max_age = NULL, domain = NULL, path = NULL, secure = NULL, http_only = TRUE, same_site = "Lax" ) remove_cookie(res, name, path = NULL, domain = NULL)
res |
An httpResponse object |
name |
A cookie-name can be any US-ASCII characters,
except control characters, spaces, or tabs. It also must
not contain a separator character like the following:
[ ]: R:%20 |
value |
A cookie-value can optionally be wrapped in double quotes and include any US-ASCII characters excluding control characters, Whitespace, double quotes, comma, semicolon, and backslash. |
expires |
The maximum lifetime of the cookie as an HTTP-date timestamp. Please enter an ISO 8601 datetime format. |
max_age |
Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. If both Expires and Max-Age are set, Max-Age has precedence. |
domain |
Host to which the cookie will be sent. |
path |
A path that must exist in the requested URL,
or the browser won't send the Cookie header. |
secure |
Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistent to man-in-the-middle attacks. |
http_only |
Forbids JavaScript from accessing the
cookie, for example, through the Document.cookie property.
Defaults to |
same_site |
Controls whether a cookie is sent with
cross-origin requests, providing some protection against
cross-site request forgery attacks (CSRF). Defaults to |
the httpResponse, with a cookie header
get_cookies() to read them back, and vignette("cookies") for
carrying a session across pages.
set_cookie( shiny::httpResponse(), "this", 12 )set_cookie( shiny::httpResponse(), "this", 12 )