webR logo
Shiny Without a Server:
webR & Shinylive

George Stagg
Senior Software Engineer, Posit PBC

Running Shiny without a server!

But what is a “server” anyhow?

Traditional Shiny architecture

A server is a machine, connected to the Internet, that runs 24/7 ready to run your Shiny app.

Some Shiny app servers



Static web hosting

Some web services offer an excellent mix of scalability, features, and cost:

However, they can’t run traditional Shiny apps!

This is static hosting. No way to run dynamic R or Python code.

Wouldn’t it be great if we could run Shiny apps using static hosting?

Shinylive architecture



How does it work?

Some technical details

WebAssembly (2017)

  • A portable binary code format
  • Enables high-performance applications on web pages
  • Near-native execution speed
  • Supported by most modern browsers

Benefits of Wasm:

  • Containerisation

  • Notebook & literate programming

  • Universal Binaries

  • Sandboxing

  • Numerical Reproducibility

  • Mobile / Tablet computing

Emscripten (2012)

  • C/C++ compiler for WebAssembly
  • Based on LLVM/Clang
  • Originally designed for ASM.js in web browsers (predates Wasm!)
  • Compiler toolchain for many browser based Wasm projects:
    SQLite, FFmpeg, DOSBox, …

Emscripten takes C or C++ source code as input, provides a Unix-like environment, and produces Wasm as output

Difficulties of Wasm

Observation: Many programming language interpreters are written in C or C++ for Unix/Linux…

BUT: For something like Python or R it’s harder than it first seems

  • Standard & support libraries (e.g. linear algebra)

  • Legacy Fortran code

  • Graphics & font support

  • Network sockets

  • Local file I/O

  • Threading and forking

  • Limited methods to wait for input

Wasm/Emscripten looks like Unix, but browser security limitations are always there.

We have to work around them, replacing standard OS tools with the provided browser APIs.


The pyodide logo

Pyodide is a port of CPython to WebAssembly

Pyodide makes it possible to install and run Python packages in the browser

Supported scientific packages include numpy, pandas, scipy, matplotlib, and scikit-learn.


The webR logo

WebR is a version of the R interpreter built for WebAssembly

Execute R code directly in a web browser, without a supporting R server

Alternatively, run an R process server-side using Node.js

Uses for webR without Shiny

We’ll come back to Shiny apps later!

WebR Demo Application 🔗 https://webr.r-wasm.org/v0.2.1/


  • R code editor, multi document support, syntax highlighting

  • Context aware autocompletion

  • Folder and file management tools

  • Mulitple plots, manage and clear plot history

  • Screenreader support in R console

  • Works on wide-ranging devices: mobile, tablet, chomebook

The webR Quarto extension

  • Knowledge of web development is not required to use webR in your own content

coatless/quarto-webr — James Balamuta

The webR Quarto extension

In a terminal,

quarto add coatless/quarto-webr

In a Quarto doc,

fit = lm(mpg ~ am, data = mtcars)

Live and interactive R code

Live and interactive R plotting

Modern text rendering features and internationalisation

Modern text rendering features and internationalisation

  • Any font family available to the web browser can be used in plots

  • Accurate font metrics for text sizing and positioning

  • Advanced text features such as ligatures & colour emoji 😃

  • RTL text and automatic font fallback for international scripts

Integration of webR with other web frameworks

const ret = await webR.evalR("penguins");
const data = await ret.toJs();
const penguins = data.values[0].values.map((_, idx) => {
  return {
    species: data.values[0].values[idx],
    island: data.values[1].values[idx],
    bill_length_mm: data.values[2].values[idx],
    bill_depth_mm: data.values[3].values[idx],
    sex: data.values[6].values[idx],

Plot.dot(penguins, {
  x: "bill_length_mm",
  y: "bill_depth_mm",
  stroke: "species", symbol: "species",
  channels: {island: "island", sex: "sex"},
  tip: true
}).plot({ grid: true, symbol: { legend: true } })

Integration of webR with other web frameworks


R packages

Binary R packages for Wasm are available, hosed at https://repo.r-wasm.org

webR 0.2.1: 10324 packages (about 51% of CRAN) - Note: not all have been tested

Shiny in webR

Service Workers are a JavaScript API that enables Shiny to work with webR.

Tricky to set up, especially for non-JavaScript developers.

Getting started with Shinylive for R

Anyone can create their own serverless Shiny apps!

Anyone can create their own serverless Shiny apps!

Shinylive Online editor 🔗 https://shinylive.io/r/examples/

Shinylive Online editor: Sharing apps

Share a Shiny app with anyone using a single URL: Example Shinylive App

Share a Shiny app from a GitHub Gist: https://shinylive.io/py/app/#gist=e62218aa28bf26e785fc6cb99efe8efe

🔗 App source code

Convert a Shiny app

Install the Shinylive R package:



Convert the app:

shinylive::export("myapp", "site")

Preview the application:


Static file output:

Ready to be uploaded to GitHub Pages or other static web hosting

Shinylive Quarto extension

First, in a terminal run the command:

quarto add quarto-ext/shinylive

Insert your Shiny app directly into the Quarto document

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.

#| standalone: true

# Create Shiny UI
ui <- [...]

# Create Shiny server function
server <- function(input, output, session) {

# Build Shiny app
shinyApp(ui = ui, server = server)

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit laborum.
#| standalone: true
#| viewerHeight: 700


theme <- bs_theme(font_scale = 1.5)

# Define UI for app that draws a histogram ----
ui <- page_sidebar(theme = theme,
  sidebar = sidebar(open = "open",
    numericInput("n", "Sample count", 50),
    checkboxInput("pause", "Pause", FALSE),
  plotOutput("plot", width=1100)

server <- function(input, output, session) {
  data <- reactive({
    if (!isTRUE(input$pause)) {
  output$plot <- renderPlot({
      breaks = 30,
      xlim = c(-2, 2),
      ylim = c(0, 1),
      xlab = "value",
      freq = FALSE,
      main = ""
    x <- seq(from = -2, to = 2, length.out = 500)
    y <- dnorm(x)
    lines(x, y, lwd=1.5)
    lwd <- 5
    abline(v=0, col="red", lwd=lwd, lty=2)
    abline(v=mean(data()), col="blue", lwd=lwd, lty=1)

    legend(legend = c("Normal", "Mean", "Sample mean"),
      col = c("black", "red", "blue"),
      lty = c(1, 2, 1),
      lwd = c(1, lwd, lwd),
      x = 1,
      y = 0.9
  }, res=140)

# Create Shiny app ----
shinyApp(ui = ui, server = server)

Future work and current issues

  • Shinylive is experimental! Things are still very much in flux
  • Loading R packages works, but is very slow. We’re working on it!

  • Not all R packages work in Wasm

  • Browser security restrictions: very limited networking, no raw socket access

  • Moving data into and out of the virtual Wasm environment is clunky right now (at best!)

  • There are no secrets with a Shinylive app!

🔗 webR demo website


🌎 Shinylive for R examples


📦 NPM (for JS Developers)

npm install webr

📙 Docs