shiny-r-optimize
Diagnose and fix performance problems in Shiny for R apps — slow startup, sluggish interactions, long-running blocking operations, and apps that struggle…
Skill by Garrick Aden-Buie (@gadenbuie)
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/posit-dev-shiny-r-optimize
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: debugging, backend-development
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/shiny-r-optimize
What's inside
- Skill name
shiny-r-optimize- Description
- Diagnose and fix performance problems in Shiny for R apps — slow startup, sluggish interactions, long-running blocking operations, and apps that struggle beyond a handful of concurrent users. Use when a Shiny app is slow, hangs, recomputes too much, times out, or needs to support more users; when the user asks to "speed up", "optimize", or "make faster" an existing Shiny app; or when preparing a prototyped app for real-world traffic.
- License
- MIT
- Metadata
- author: Garrick Aden-Buie (@gadenbuie) · version: 1.0
- Instructions
- 213 lines in SKILL.md · view on GitHub
Files (7)
- SKILL.md
- async-tasks.md · references/
- caching.md · references/
- data-loading.md · references/
- diagnosis.md · references/
- reactive-graph.md · references/
- rendering-ui.md · references/
SKILL.md (first lines)
# Optimizing Shiny App Performance
Make an existing Shiny for R app faster without changing what it does. The
typical user has an app that worked fine for one or two developers and is now
too slow or must support more users. Keep all app logic intact; prefer code
changes over deployment tuning; resist rewrites.
Order of preference for every fix:
1. **Eliminate** work that isn't needed (don't compute outputs nobody is
looking at; don't compute with inputs that aren't ready).
2. **Reuse** work that repeats (shared reactives, caching).
3. **Accelerate** unavoidable work (faster data formats, vectorized code).
4. **Relocate** work that blocks (async / background tasks) — last resort; it
buys concurrency, not raw speed.
## Workflow
### 1. Understand the complaint
Pin down what "slow" means and write it as a benchmark to beat:
- **What feels slow?** First page load? Every interaction? One specific
action? Only when several people use it at once?
- **How many concurrent users** must it support? (1–5, ~20, hundreds?)
- **Where does it run?** Locally, Posit Connect, Posit Connect Cloud, Shiny
Server?
- **How big is the data**, where does it live, and how often does it change?
### 2. Scan the source for smells — and fix them now
Read the app code (`app.R` / `server.R` / `ui.R` / modules) and look for the
classic smells. Every smell in this table has a **safe, behavior-preserving
fix**: apply each fix you find *immediately*, before measuring. These ten
changes fix the large majority of slow Shiny apps.
| Smell (search for) | Why it hurts | Fix |
|---|---|---|
| `read.csv`/`read_csv`/DB queries **inside** `server()` or render functions | Re-runs per session or per invalidation | Load once in global scope → [data-loading.md](references/data-loading.md) |
| Same subsetting/aggregation repeated in several outputs | Duplicate computation on every change | Extract one shared `reactive()` → [reactive-graph.md](references/reactive-graph.md) |
| Renderers defined **inside** `observeEvent()`/`observe()` | Re-creates outputs; fights the reactive model | Define once at server top level, gate with `bindEvent()`/`req()` → [reactive-graph.md](references/reactive-graph.md) |
| Expensive `render*` with no `req()` guard | Computes on empty/half-ready inputs at startup | `req()` at the top of the body → [reactive-graph.md](references/reactive-graph.md) |
| Many heavy outputs on the landing view | All must compute before the page is usable | Move to tabs/navs — hidden outputs don't compute → [rendering-ui.md](references/rendering-ui.md) |
| No `bindCache()` on repeated identical expensive calls | Everyone re-pays the same cost | `bindCache()` → [caching.md](references/caching.md) |
| Sliders/text inputs feeding expensive chains directly | One drag = dozens of recomputations | `debounce()`/`throttle()`/`bindEvent()` → [reactive-graph.md](references/reactive-graph.md) |
| `Sys.sleep`, long model fits, slow API/DB calls inline in reactives/renders | Blocks the whole R process for all users | `ExtendedTask` → [async-tasks.md](references/async-tasks.md) |
| `renderUI()` used for routine value updates | DOM rebuild + input rebinding per update | `update*Input()` → [rendering-ui.md](references/rendering-ui.md) |
| Wide `isolate()`-free reads in `eventReactive`/`observeEvent` | Invalidates more than necessary | Narrow dependencies → [reactive-graph.md](references/reactive-graph.md) |
Apply the fixes in this rough order (cheapest first): data loads to global
scope → `req()` guards → shared reactives → tabs for heavy outputs →
`bindCache()` → debounce chatty inputs → `update*Input()` over `renderUI` →
faster data formats (`fread`/feather/DuckDB) → `ExtendedTask` for multi-second
operations → busy indicators (`bslib::useBusyIndicators()`) for remaining
waits.
### 3. Measure — to confirm, not to gate
Measurement is for confirming fixes and for finding what the scan can't see.
**It must never crowd out action.** If the app is hard to run, profiling is
slow, or results are ambiguous, apply the scan fixes anyway and say what
couldn't be verified.
```r
# Where does the time go? (CPU/memory profile)
p <- profvis::profvis(shiny::runApp("app.R"))
debrief::pv_print_debrief(p) # debrief turns the profvis output into text
# summaries — hot functions/lines, call paths,
# memory, suggestions — much easier to act on
# than the interactive flame graph. Follow up
# with pv_focus(), pv_hot_lines(),
# pv_suggestions(); compare before/after runs
# with pv_print_compare().
# Is implementation A faster than B?
bench::mark(A(x), B(x), check = FALSE)
```
Two tools in the ecosystem are for the **user**, not for you to run: **reactlog**
has the user interact with their own app and explore the reactive-graph
visualization to understand how their reactives interact — you can usually get
the same insight (and more cheaply) by reading the code and reconstructing the
reactive graph yourself. **shinyloadtest** is a load test of a *deployed* app,
run at the end of an optimization when the user wants to verify their
infrastructure will handle the number of users they intend to support.
Tool selection, how to read each tool's output, and load-test interpretation:
[diagnosis.md](references/diagnosis.md). Use profvis/debrief for "which code is
slow" and bench to compare two implementations; leave reactlog to the user,
and shinyloadtest for deployed-app capacity.
### 4. Diagnose what the scan didn't catch
| Symptom | Most likely causes | Start here |
|---|---|---|
| First page load is slow (every session) | Data loading/prep inside `server()`; outputs computing at startup on empty inputs; heavy setup per session | [data-loading.md](references/data-loading.md), [reactive-graph.md](references/reactive-graph.md) |
| One interaction recomputes far too much | Over-broad dependencies; duplicate computation; no caching; chatty inputs | [reactive-graph.md](references/reactive-graph.md), [caching.md](references/caching.md) |
| A specific action takes seconds and the app freezes | Long synchronous operation in the flush cycle | [async-tasks.md](references/async-tasks.md) |
| Fast for one user, degrades with several | Blocking operations tie up the process; repeated identical computation; per-user reloads | [caching.md](references/caching.md), [async-tasks.md](references/async-tasks.md), [diagnosis.md](references/diagnosis.md) |
| Plots/tables slow to appear or update | Heavy rendering; full redraws; `server = FALSE` DT on big data | [rendering-ui.md](references/rendering-ui.md) |
| Downloads/uploads misbehave or recompute | `downloadHandler` recomputing; `maxRequestSize` cap | [data-loading.md](references/data-loading.md) |
| Slower only when deployed | Per-process globals and caches; scheduler settings | [diagnosis.md](references/diagnosis.md) |
### 5. Fix, cheapest change first
Work the ladder for the diagnosed category; stop when measurements say it's
fast enough. Every rung preserves app behavior.
**Reactive recomputation** → [reactive-graph.md](references/reactive-graph.md)
1. `req()` guards — nothing computes on not-ready inputs.
2. Shared `reactive()`s — each derived value computed once.
3. Narrow dependencies: `bindEvent()`/`eventReactive()`, `isolate()`,
`freezeReactiveValue()` for update loops.
4. `debounce()`/`throttle()` chatty inputs; when updates compound, gate
the batch behind `bslib::input_task_button()` (drop-in for
`actionButton()`).
5. Fix timers: `on.exit(invalidateLater(...))`, `reactivePoll()` with a cheap
`checkFunc`.From posit-dev/skills, MIT.
More from posit-dev/skills
- 0.815score
r-lifecycle
Garrick Aden-Buie (@gadenbuie)
Guidance for managing R package lifecycle according to tidyverse principles using the lifecycle package.
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
r-testthat
Garrick Aden-Buie (@gadenbuie)
Best practices for writing R package tests with testthat version 3+.
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
pr-create
Garrick Aden-Buie (@gadenbuie)
Creates a pull request from current changes, monitors GitHub CI, and debugs any failures until CI passes.
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
r-cli
Garrick Aden-Buie (@gadenbuie)
Comprehensive R package for command-line interface styling, semantic messaging, and user communication.
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
shiny-py-optimize
Garrick Aden-Buie (@gadenbuie)
Diagnose and fix performance problems in Shiny for Python apps — slow startup, sluggish interactions, blocking operations, and apps that struggle beyond a…
SkillAFreshness: A★ 547Claude Code +9 - 0.809score
r-cran-extrachecks
Garrick Aden-Buie (@gadenbuie)
Prepare R packages for CRAN submission by checking for common ad-hoc requirements not caught by devtools::check().
SkillAFreshness: A★ 547Claude Code +9 - 0.809score
critical-code-reviewer
Garrick Aden-Buie (@gadenbuie)
Rigorously review code or pull requests for correctness, security, accessibility, maintainability, tests, and edge cases.
SkillAFreshness: A★ 547Claude Code +9 - 0.809score
review-testing
Garrick Aden-Buie (@gadenbuie)
Review test code for quality, design, and completeness after implementing a feature or fixing a bug.
SkillAFreshness: A★ 547Claude Code +9 - 0.809score
r-mirai
Charlie Gao (@shikokuchuo)
Help users write correct R code for async, parallel, and distributed computing using mirai.
SkillAFreshness: A★ 547Claude Code +9 - 0.809score
r-cran-status
Garrick Aden-Buie (@gadenbuie)
Look up an R package's live status on cran.r-project.org - submission/review state (queue, human review, waiting, archived, past version's fate) or R CMD…
SkillAFreshness: A★ 547Claude Code +9 - 0.809score
describe-design
Garrick Aden-Buie (@gadenbuie)
Research a codebase and create architectural documentation describing how features or systems work.
SkillAFreshness: A★ 547Claude Code +9 - 0.806score
pr-threads-resolve
Barret Schloerke (@schloerke)
Bulk resolve unresolved PR review threads on the current branch’s PR — typically after threads have been addressed manually or via /pr-threads-address
SkillAFreshness: A★ 547Claude Code +9
Signals
- Stars
- 547
- Forks
- 57
- Open issues
- 6
- Last commit (this folder)
- Oct 14, 2026 (13 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- Version
- 1.0
- License
- MIT
- Repository
- github.com/posit-dev/skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from Garrick Aden-Buie (@gadenbuie)
- 0.815score
shiny-py-optimize
Garrick Aden-Buie (@gadenbuie)
Diagnose and fix performance problems in Shiny for Python apps — slow startup, sluggish interactions, blocking operations, and apps that struggle beyond a…
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
r-testthat
Garrick Aden-Buie (@gadenbuie)
Best practices for writing R package tests with testthat version 3+.
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
r-lifecycle
Garrick Aden-Buie (@gadenbuie)
Guidance for managing R package lifecycle according to tidyverse principles using the lifecycle package.
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
r-cli
Garrick Aden-Buie (@gadenbuie)
Comprehensive R package for command-line interface styling, semantic messaging, and user communication.
SkillAFreshness: A★ 547Claude Code +9 - 0.815score
pr-create
Garrick Aden-Buie (@gadenbuie)
Creates a pull request from current changes, monitors GitHub CI, and debugs any failures until CI passes.
SkillAFreshness: A★ 547Claude Code +9 - 0.809score
critical-code-reviewer
Garrick Aden-Buie (@gadenbuie)
Rigorously review code or pull requests for correctness, security, accessibility, maintainability, tests, and edge cases.
SkillAFreshness: A★ 547Claude Code +9
Cite this page
shiny-r-optimize skill. Agent Mart. https://agentmart.dev/skills/posit-dev-shiny-r-optimize (last updated 2026-10-24)