Shiny without Boundaries

One App, Multiple Destinations

James Balamuta

Guest Lecture STAT 447 @ UIUC

April 9th, 2025 @ 4 PM CDT / 6 PM PDT

Overview

In this lecture, we’ll have a conservation about:

  • Shiny applications and modernization
  • The challenge of reaching users where they are
  • Seven deployment pathways for Shiny applications
    • Implementation strategies for each approach
    • Trade-offs and considerations

Shiny

What is Shiny?

Two Perspectives

How it Works

Monolithic Structure (app.R)

Modular Structure (ui.R/server.R)

The Reactive Heart of Shiny

Reactivity Invalidation

Modernizing Shiny

The Shiny Ecosystem

bslib

Responsive Design with bslib

Layouts

Theming

bslib Simplifies Customization

Some Examples

These examples (minus the last one) are all deployed using Shinylive.

Async Shiny

The Deployment Challenge

Users have diverse environment constraints

Technical teams have different infrastructure preferences

Security and compliance requirements vary

Need for offline vs. online access

Cost and maintenance considerations

Why Multiple Deployment Options?

  • Reach users where they are: Not everyone can use cloud deployments
  • Accommodate different technical environments: Corporate restrictions, air-gapped systems
  • Scale appropriately: From individual users to enterprise-wide deployment
  • Leverage existing infrastructure: Use what’s already available
  • Optimize user experience: Native-like experiences when needed

Use-case: Healthcare Data Analytics

Deployment Pathways

Seven Deployment Pathways

  1. On-Premise Servers
  2. Cloud Services (Shiny Server, Posit Connect, shinyapps.io)
  3. Docker Containers
  4. Electron Desktop Applications
  5. WebAssembly Browser Solutions
  6. R Packages
  7. Quarto Integration

1. On-Premise Servers

What is an On-Premise Server?

Using an On-Premise Server

3 Step Setup for On-Premise Servers

On-Premise Servers: Advantages & Considerations

Advantages:

  • Complete control over infrastructure
  • Enhanced security and compliance
  • No external dependencies
  • Customizable configuration
  • Integration with internal systems

Considerations:

  • Infrastructure costs
  • Maintenance burden
  • Scaling challenges
  • Backup and disaster recovery
  • Update management

2. Cloud Services

What Is A Cloud Server?

On-Premise vs. Cloud: What’s the Difference?

Deploying to the Cloud

Cloud Services: Advantages & Considerations

Advantages:

  • Easy deployment process
  • Managed scaling
  • Built-in security
  • No infrastructure maintenance
  • Logs and metrics

Considerations:

  • Subscription costs
  • Internet connectivity required
  • Potential data privacy concerns
  • Limited customization
  • Dependency on third-party service

3. Docker Containers

What’s Docker?

Docker: Shiny App Workflow

shinydocker: One Command to Rule Them All

Demo {shinydocker}

Docker Containers: Advantages & Considerations

Advantages:

  • Environment consistency
  • Scalability
  • Portability across systems
  • Isolation from host system
  • Works with container orchestration

Considerations:

  • Learning curve
  • Container management overhead
  • Resource requirements
  • Networking complexity
  • Persistent data management

4. Electron Desktop Applications

Electron Overview

  • Framework for creating native applications with web technologies
  • Combines Chromium (rendering) and Node.js (backend)
  • Perfect for packaging Shiny as desktop applications

Electronify a Shiny App

Demo {shinyelectron} - WIP

Electron Apps: Advantages & Considerations

Advantages:

  • Offline capability
  • Native desktop experience
  • OS integration (file system, notifications)
  • Self-contained installation
  • No web server required

Considerations:

  • Larger application size
  • Platform-specific builds
  • Update distribution
  • Increased complexity
  • Performance considerations

5. WebAssembly Browser Solutions

WebAssembly and Shiny

  • Shinylive allows Shiny apps to run entirely in the browser
    • Developed by Winston Chang, George Stagg, Barret Schloerke, and Garrick Aden-Buie.
  • No compute server required - uses WebAssembly
  • Benefits:
    • Zero deployment costs
    • Reduced maintenance overhead
    • Better scalability
    • Works offline after initial load

Shinylive Playground

🔗 https://shinylive.io/r/editor

A screenshot of the shinylive editor in a web browser

Shinylive Apps

The code that makes up the app is encoded in the URL. As a result, you can copy and paste it to share with others.

How Does it Work?

  • Uses WebR (R compiled to WebAssembly)
    • Developed by George Stagg and Lionel Henry
  • Includes necessary R packages in the browser
  • Everything runs client-side:
    • R interpreter
    • Shiny package
    • Your application code

Size Considerations

  • Base size: ~60MB
  • Includes:
    • WebR base
    • Shiny package dependencies
    • Your app code and dependencies
pkg_db <- tools::CRAN_package_db()
shiny_pkg_dependencies <- tools::package_dependencies(
  packages = "shiny",
  recursive = TRUE,
  db = pkg_db
)

shiny_pkg_dependencies
#> $shiny
#>  [1] "methods"     "utils"       "grDevices"   "httpuv"      "mime"       
#>  [6] "jsonlite"    "xtable"      "fontawesome" "htmltools"   "R6"         
#> [11] "sourcetools" "later"       "promises"    "tools"       "crayon"     
#> [16] "rlang"       "fastmap"     "withr"       "commonmark"  "glue"       
#> [21] "bslib"       "cachem"      "ellipsis"    "lifecycle"   "base64enc"  
#> [26] "jquerylib"   "memoise"     "sass"        "digest"      "Rcpp"       
#> [31] "cli"         "magrittr"    "stats"       "graphics"    "fs"         
#> [36] "rappdirs"

Deployment Options

  1. GitHub Pages
    • Free hosting
    • Automatic deployment via GitHub Actions
    • Up to 1GB limit
  2. Quarto Pub
    • Free hosting
    • Publish via quarto publish
    • Must use Quarto documents
  3. Any Static Web Host

Deployment Types

  1. Single-file Apps
    • Entire app in a single R script
    • Easy to deploy and share
    • Suitable for small apps
  2. Multi-file Apps
    • Split into ui.R and server.R
    • More modular and maintainable
    • Requires additional configuration
  3. Quarto Integration
    • Embed apps in Quarto documents
    • Seamless integration
    • Ideal for tutorials and reports

Standalone Apps

Shiny App

We’ll begin by creating a Shiny app, converting it to a Shinylive app, and deploying it to GitHub Pages.

Demo App

We’ll use the following Shiny app that randomly samples from a normal distribution and plots the histogram and density curve as an example.

app.R

library(shiny)
library(bslib)

ui <- page_sidebar(
  sidebar = sidebar(
    numericInput("n", "Sample size", 100),
    checkboxInput("pause", "Pause", FALSE)
  ),
  plotOutput("plot")
)

server <- function(input, output) {
  data <- reactive({
    input$resample
    if (!isTRUE(input$pause)) {
      invalidateLater(1000)
    }
    rnorm(input$n)
  })
  
  output$plot <- renderPlot({
    hist(data(), freq = FALSE)
    lines(density(data()))
  })
}

shinyApp(ui, server)

Install Shinylive

The shinylive package can be installed from CRAN by using:

install.packages("shinylive")

You do not need any other packages to create Shinylive apps.

Convert to Shinylive

To convert a Shiny app to a Shinylive app, use the shinylive::export function:

shinylive::export(".", "_site")

The first argument is the directory containing the Shiny app, and the second is the output directory. We’re assuming the app is in the current working directory, e.g. "." and the output directory is "_site".

View locally

To view the app locally, we’ll need to use a live (local) server. One option is to use the httpuv package:

httpuv::runStaticServer("_site")

This will start a local server at http:// followed by an IP address and port number.

Directly opening the index.html file in a browser will not work due to security restrictions. Also, do not use shiny::runApp() to view Shinylive apps as it will not work.

Shinylive App Structure

Apps are converted from .R and stored in app.json with the following structure:

[
  {
    "name": "app.R",
    "content": "library(shiny)\nui <- fluidPage(...)\nserver <- function(input, output) {...}\nshinyApp(ui, server)",
    "type": "text"
  }
]

Components:

  • name: File name (app.R, ui.R, server.R, etc.)
  • content: File contents (escaped)
  • type: Usually “text”

Single vs Multi-file Apps JSON

Single file (app.R)

[
  {
    "name": "app.R",
    "content": "library(shiny)\n...",
    "type": "text"
  }
]

Multi-file (ui.R & server.R)

[
  {
    "name": "server.R",
    "content": "server <- function(input, output) {...}",
    "type": "text"
  },
  {
    "name": "ui.R",
    "content": "ui <- fluidPage(...)",
    "type": "text"
  }
]

Inspecting Shinylive Apps

Steps to convert a Shinylive app back to a Shiny app:

  1. Locate the app.json file
    • Add to the end of the URL /app.json to download.
  2. Parse JSON structure
    • Convert to R list using jsonlite::fromJSON()
  3. Extract file contents
    • Get the content field for each file
  4. Reconstruct app
    • Write to disk or use in R session

{peeky} R package automates this process.

There Are …

No Secrets

Viewing Shinylive Apps

GitHub Pages Setup

We can deploy the app to GitHub Pages by following these steps:

  1. Create repository structure:
.
├── .github/workflows/
│   └── build-and-deploy.yml
├── README.md
├── DESCRIPTION
└── app.R
  1. Enable GitHub Pages in repository settings
  2. Configure GitHub Actions workflow

Configure GitHub Pages

Make sure to enable GitHub Pages in the repository settings with Enforce HTTPS checked!

A screenshot of the GitHub Pages settings in a GitHub repository for R Shinylive

GitHub Actions Workflow

build-and-deploy.yml

on:
  push:
    branches: [main, master]
    
name: shinylive-deploy

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pages: write
      id-token: write
      
    steps:
      - uses: actions/checkout@v4
      - uses: r-lib/actions/setup-r@v2
      - name: "Setup R dependency for Shinylive App export"
          uses: r-lib/actions/setup-r-dependencies@v2
          with:
            packages:
              cran::shinylive,
              local::.
      
      - name: Export app
        run: |
          shinylive::export(".", "_site")
        shell: Rscript {0}
      
      - name: Upload Pages artifact
        uses: actions/upload-pages-artifact@v3
          with: 
            retention-days: 1

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
  1. Triggers on push to main or master
  2. Setup R and shinylive package
  3. Exports app to _site directory
  4. Deploys to GitHub Pages

Avoids retaining artifacts in main repository for faster clones!

Quarto and Shinylive

Quarto Integration

Shinylive apps can be embedded in Quarto documents using the shinylive Extension.

Download and install the Quarto extension into your Quarto project by typing in Terminal:

quarto add quarto-ext/shinylive

This will download the extension into the _extensions folder.

Quarto extensions are per-project and require either specifying the filter in the YAML header or in the _quarto.yaml project configuration file.

Quarto Integration

Add to YAML header:

format:
  html:
    resources: 
      - shinylive-sw.js
filters:
  - shinylive

Use the shinylive-r custom code cell in document:

```{shinylive-r}
#| standalone: true
library(shiny)
ui <- fluidPage(...)
server <- function(input, output) {...}
shinyApp(ui, server)
```

Publish to A Static Website

We can publish the Quarto document to a static website using the quarto publish command.

quarto publish gh-pages
quarto publish quarto-pub

Deploy from GitHub Repository

on:
  push:
    branches: [main, master]
  release:
      types: [published]
  workflow_dispatch: {}

name: demo-website

jobs:
  demo-website:
    runs-on: ubuntu-latest
    concurrency:
      group: quarto-website-${{ github.event_name != 'pull_request' || github.run_id }}
    permissions:
      contents: read
      pages: write
      id-token: write
    steps:
      - name: "Check out repository"
        uses: actions/checkout@v4

      - name: "Setup pandoc"
        uses: r-lib/actions/setup-pandoc@v2

      - name: "Setup R"
        uses: r-lib/actions/setup-r@v2

      - name: "Setup R dependencies for Quarto's knitr engine"
        uses: r-lib/actions/setup-r-dependencies@v2
        with:
          packages:
            ## Pin version to ensure consistency
            cran::shinylive@0.2.0 
            any::knitr
            any::rmarkdown
            any::downlit
            any::xml2

      - name: "Set up Quarto"
        uses: quarto-dev/quarto-actions/setup@v2

      - name: "Render working directory"
        uses: quarto-dev/quarto-actions/render@v2

      - name: Upload Pages artifact
        uses: actions/upload-pages-artifact@v3
        with: 
          retention-days: 1
      
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Similar to the Shinylive GitHub Actions workflow, but with Quarto-specific steps:

  1. Setup Quarto and dependencies
  2. Render the working directory
  3. Upload the Pages artifact
  4. Deploy to GitHub Pages

Ensure you have a _quarto.yml configuration file in the repository!

Best Practices

  1. Minimize Dependencies
    • Each package increases load time
    • Consider alternative implementations
  2. Optimize App Size
    • Compress data files
    • Use efficient data structures
  3. Test Performance
    • Check initial load time
    • Monitor memory usage
    • Test on various devices

WebAssembly Solutions: Advantages & Considerations

Advantages:

  • No server required
  • Works anywhere with a modern browser
  • No installation needed
  • Improved security (browser sandbox)
  • Reduced hosting costs

Considerations:

  • Limited package support
  • Performance constraints
  • Initial load time
  • Browser compatibility
  • Memory limitations

6. R Packages

What is an R package?

Cover of the R packages textbook

Packages are the fundamental units of reproducible R code. They include reusable R functions, the documentation that describes how to use them, and sample data.

– Hadley Wickham and Jennifer Bryan in R packages (2e)

R Code vs. R Packages

Shipping analogy of loose freight compared to freight placed inside of containers.

Deploying with an R Package

R Packages: Advantages & Considerations

Advantages:

  • Reproducible installation
  • Version control
  • Documentation integration
  • Easy distribution to R users
  • Works within existing R workflows

Considerations:

  • Requires R knowledge
  • Regular updates needed
  • Limited to R ecosystem
  • Dependency management
  • Requires local R installation

7. Quarto Integration

Quarto and Shiny

  • Quarto can be used with Shiny directly.
    • This is the most common use case if Posit Connect is available.
  • Quarto documents can include Shiny code cells through Execution Contexts.
    • This allows for embedding Shiny apps in Quarto documents.

This is popular for Shiny dashboards.

You are better off deploying a Shiny app as a standalone app and linking to it from a Quarto document if using in a report.

You must have a Shiny server (compute server) running to use Quarto with Shiny outside of the Shinylive context.

Setup

---
title: "Interactive Report with Embedded Shiny"
format: html
server: shiny
---

Document YAML Options:

  • format: html - Output format
  • server: shiny - Enables interactivity

Server-Side Processing

```{r}
#| eval: false
#| context: server
#| code-line-numbers: "|3|6-8|10-15"

dataset <- reactive({
  mtcars[sample(nrow(mtcars), input$n), ]
})

# Define output
output$scatterPlot <- renderPlot({
  ggplot(dataset(), aes(x = wt, y = mpg)) +
    geom_point() +
    geom_smooth(method = "lm", se = FALSE)
})
```

This code:

  1. Operates under the **context: server** execution context
  2. Creates a reactive dataset that changes with user input
  3. Defines a scatter plot with a regression line

User Input Component

sliderInput("n", 
            "Number of cars:", 
            min = 5, max = 32, value = 20)

The slider:

  • Creates an input named "n"
  • Controls sample size from 5 to 32 cars
  • Starts with 20 cars selected

By default, the context chosen is that of the UI. Hence, we do not need to specify it.

Output Display

```{r}
#| eval: false
#| context: server
#| panel: fill
#| code-line-numbers: "|3|7"

plotOutput("scatterPlot")
```

The output:

  • panel: fill makes the plot fill available space
  • plotOutput displays our reactive scatter plot

The Complete Example

---
title: "Interactive Report with Embedded Shiny"
format: html
server: shiny
---

## Interactive Analysis

```{r}
#| context: server
dataset <- reactive({
  mtcars[sample(nrow(mtcars), input$n), ]
})
# Define output
output$scatterPlot <- renderPlot({
  ggplot(dataset(), aes(x = wt, y = mpg)) +
    geom_point() +
    geom_smooth(method = "lm", se = FALSE)
})
```

```{r}
sliderInput("n", "Number of cars:", 
            min = 5, max = 32, value = 20)
```

```{r}
#| context: server
#| panel: fill
plotOutput("scatterPlot")
```

Quarto Integration: Advantages & Considerations

Advantages:

  • Seamless narrative + interactivity
  • Reproducible research
  • Easy publishing options
  • Code and visualization in one document

Considerations:

  • Learning Quarto syntax
  • Limited to document context
  • Performance with large documents
  • Complex interactivity limitations
  • Deployment considerations similar to Shiny

Concluding

Resources

Q&A

Thank you! Any questions?

License

This work is licensed under CC BY-NC-ND 4.0

CC BY-NC-ND 4.0 license logo