library(shiny)
library(ggplot2)
ui <- fluidPage(
titlePanel("Sampling distribution explorer"),
sidebarLayout(
sidebarPanel(
h4("Simulation settings"),
sliderInput("n", "Sample size (n)",
min = 2, max = 500, value = 30),
helpText("Larger values of n produce a narrower sampling distribution."),
sliderInput("reps", "Number of replications",
min = 200, max = 5000, value = 1000),
selectInput("dist", "Population",
choices = c("Normal" = "norm",
"Exponential" = "exp",
"Uniform" = "unif")),
helpText("The central limit theorem applies regardless of population shape
when n is large enough.")
),
mainPanel(
plotOutput("sdplot"),
p("The red curve shows a normal approximation with the theoretical
standard error 1/sqrt(n).")
)
)
)
server <- function(input, output) {
means <- reactive({
draw <- switch(input$dist,
norm = function(n) rnorm(n),
exp = function(n) rexp(n) - 1, # centre at 0
unif = function(n) runif(n) - 0.5)
replicate(input$reps, mean(draw(input$n)))
})
output$sdplot <- renderPlot({
m <- means()
se <- 1 / sqrt(input$n)
df <- data.frame(m = m)
ggplot(df, aes(x = m)) +
geom_histogram(aes(y = after_stat(density)),
bins = 50, fill = "steelblue", colour = "white") +
stat_function(fun = dnorm,
args = list(mean = 0, sd = se),
colour = "firebrick", linewidth = 1) +
labs(x = "Sample mean", y = "Density") +
theme_minimal()
})
}
shinyApp(ui, server)Polishing, Deployment, and Moving Forward
This guide covers the steps required to make a Shiny application ready for other people to use, including adding clear instructions, improving layout, and deploying to shinyapps.io. It also reviews systematic debugging strategies and maps out the most productive topics to pursue after completing the course.
Adding instructions and context
An application intended for others needs enough built-in guidance that a user can understand what the controls do without asking the developer. Several UI functions help with this.
titlePanel sets the application’s main heading. helpText adds a small grey paragraph inside any panel, typically placed below a control to explain what it does. h3, h4, and p produce HTML headings and paragraphs and can be used anywhere in the UI to add structure.
Showing progress for slow computations
When a reactive expression takes more than a second or two, users need feedback that something is happening. withProgress and incProgress display a progress bar in the browser.
library(shiny)
ui <- fluidPage(
sliderInput("reps", "Number of replications", min = 500, max = 20000, value = 5000),
sliderInput("n", "Sample size", min = 2, max = 200, value = 30),
actionButton("go", "Run simulation"),
plotOutput("hist")
)
server <- function(input, output) {
results <- eventReactive(input$go, {
withProgress(message = "Running simulation...", value = 0, {
out <- numeric(input$reps)
for (i in seq_len(input$reps)) {
out[i] <- mean(rnorm(input$n))
if (i %% 100 == 0) incProgress(100 / input$reps)
}
out
})
})
output$hist <- renderPlot({
hist(results(), col = "steelblue", border = "white",
xlab = "Sample mean", main = "Sampling distribution")
})
}
shinyApp(ui, server)withProgress opens a progress bar in the browser with the given message. incProgress advances the bar by the specified fraction of the total (0 to 1). The eventReactive wraps the computation so it only runs when the button is clicked, not on every slider change, which is appropriate here because the simulation takes several seconds at large reps.
For most teaching examples a progress bar is unnecessary, but it is worth knowing for any application that does heavy computation.
Deploying to shinyapps.io
shinyapps.io is a hosting service run by Posit that allows you to publish Shiny applications as public (or private) web pages without managing a server. The free tier supports up to five applications and a limited number of active hours per month, which is sufficient for sharing course work.
Step 1. Create an account at https://www.shinyapps.io.
Step 2. In R, install the rsconnect package if it is not already installed.
install.packages("rsconnect")Step 3. Register your shinyapps.io account with rsconnect. In the shinyapps.io dashboard, go to your account settings and create a token. You will be shown a name, a token string, and a secret string. Pass these to setAccountInfo:
rsconnect::setAccountInfo(name = "your-username",
token = "your-token",
secret = "your-secret")This writes the credentials to a small configuration file under your R config directory and you only need to run it once per machine. Every subsequent rsconnect call reads from that file automatically. The exact path depends on your system, but on Linux it is typically ~/.config/R/rsconnect/accounts/shinyapps.io/.
Step 4. In RStudio, open your app.R file. Click “Publish” in the top-right corner of the source pane (or in the Viewer after running the app locally) and follow the prompts.
Step 5. Choose a name for the application and click “Publish”. RStudio bundles the application and its dependencies, uploads them, and opens the live URL in your browser.
Alternatively, deploy from the console:
rsconnect::deployApp(appDir = "path/to/your/app")Deploying to your own VPS
Running Shiny Server on a virtual private server gives you complete control over the environment and no usage limits. The trade-off is that you are responsible for the server: keeping it updated, installing R and packages, and configuring the web server. What follows describes the broad steps for an Ubuntu VPS using nginx, which is a common and well-documented combination.
Step 1. Provision a VPS and configure DNS. Any cloud provider offers Ubuntu VPS instances. Once the server is running and you have its public IP address, add a DNS A record at your domain registrar pointing your chosen domain or subdomain to that IP. Allow a few minutes for propagation before expecting the domain to resolve.
Step 2. Install R on the server. Log in over SSH and install R. For Ubuntu, the CRAN project provides instructions for adding the CRAN repository for the current R release; installing from there rather than the default Ubuntu repositories gives you a more recent version of R. Shiny Server has no R of its own, so this step is essential.
Step 3. Install Shiny Server. Posit distributes Shiny Server as a Debian package, available from the Shiny Server download page. Install it with dpkg or apt, then start and enable the service:
sudo systemctl enable --now shiny-serverBy default, Shiny Server listens on port 3838.
Step 4. Install your application’s R packages. Shiny Server runs as a dedicated system user rather than your personal account, so packages must be installed system-wide. Open an R session as root with sudo R and install every package the application requires. Any package present on your development machine but absent from the server will cause the application to fail at runtime, often with an unhelpful error message in the browser.
Step 5. Place the application on the server. Shiny Server looks for applications in /srv/shiny-server/ by default. Copy your application directory there, or create a symlink from a path under /srv/shiny-server/ to wherever you keep the files in your home directory. The configuration file at /etc/shiny-server/shiny-server.conf controls which directory is served and on which port; the default settings are usually sufficient to begin with.
Step 6. Configure nginx as a reverse proxy. It is standard practice to put nginx in front of Shiny Server rather than exposing port 3838 directly to the internet. Nginx accepts connections on ports 80 and 443 and forwards them to 127.0.0.1:3838. Because Shiny uses WebSockets for the connection between the browser and the server session, the nginx configuration must pass the Upgrade and Connection headers through to the backend; nginx configuration snippets for Shiny that handle this are widely available.
Step 7. Obtain an SSL certificate. Certbot, the Let’s Encrypt client, automates obtaining and renewing a free TLS certificate for your domain. Running sudo certbot --nginx -d your.domain.com both obtains the certificate and updates the nginx configuration to serve HTTPS and redirect HTTP requests automatically. Certbot installs a renewal timer so the certificate is kept up to date without manual intervention.
Once all seven steps are complete, a request to your domain travels through nginx, which forwards it to Shiny Server, which manages the R session for your application. Updating the application is then simply a matter of updating the files in the application directory; Shiny Server detects the change and restarts the session automatically.
Systematic debugging
Reactive context errors
The error “Operation not allowed without an active reactive context” means you are reading input$x outside a render*, reactive, or observe block. Move the offending code into a reactive context or pass the value as an argument to a helper function.
Missing or NULL inputs
Outputs that error or appear blank on startup often have inputs that are NULL before the user interacts. Wrap the offending render block with req(input$x) to silence the error until a valid value exists.
Outputs not updating
If changing a control has no effect on an output, check that: 1. The output’s render block actually reads the input (not just a non-reactive copy of it). 2. The render block is not wrapped in isolate({...}), which suppresses reactivity. 3. A req() call is not silently halting execution early.
The reactlog package provides the clearest picture:
options(shiny.reactlog = TRUE)
# ... run the app and interact ...
shiny::reactlogShow()Where to go next
The course has covered the foundations. The most productive next topics, roughly in order of immediate usefulness:
Shiny modules. Once an application grows beyond a few hundred lines, organising server logic becomes difficult. Modules allow you to encapsulate a piece of UI and its server logic into a reusable component with its own namespace, so IDs do not clash across sections. The official Shiny documentation has a clear introduction.
The DT package. DT::datatable produces sortable, filterable, paginated tables that are far more capable than renderTable. Selections in a DT table can trigger reactive updates elsewhere in the application.
The plotly package. plotly::ggplotly converts a ggplot object to an interactive plot with tooltips, zoom, and pan, with minimal additional code.
shinydashboard or bslib. These packages provide richer layout systems and pre-built dashboard components (value boxes, status indicators, navigation drawers). bslib is the more modern option and integrates directly with Bootstrap theming.
Performance optimisation. For applications used by many people simultaneously, look into caching with bindCache, pre-rendering expensive computations outside the reactive graph, and the shinyloadtest package for load testing.
Shiny documentation and community. The official documentation at https://shiny.posit.co is comprehensive, well-organised, and includes a gallery of example applications with source code. The Posit Community forum is the best place to ask questions.