server-side-rendering-flutter-apps-with-rfw
Learn how to build a dynamic Flutter app using Server Side Rendering (SSR) with the rfw package, enabling UI updates driven by server logic and binary data exchange via HTTP.
What this skill does
# Server Side Rendering Flutter Apps with RFW
This post will guide you how to build a Flutter app that takes advantage of Server Side Rendering (SSR) and being able to update UI dynamically.
> If you are new to flutter you can follow this [post](https://rodydavis.com/posts/first-flutter-project) on getting started
This technique will use the [rfw](https://pub.dev/packages/rfw) package on the server and client to send binary data via HTTP requests.
> **TLDR** You can find the final source [here](https://github.com/rodydavis/flutter_ssr).
## Getting started
Create a new directory called `flutter_ssr` and navigate to it in terminal or open it up in your favorite IDE.
## Approaches to updates
If you are in the mobile world then you know how challenging it can be to get all your users on the latest version. Even just having an API or database schema can be very hard to update because of users on older versions (sometimes due to OS limitations).
### Code Push
[Shorebird](https://shorebird.dev/) takes an interesting approach to delivering updates to the users via Code Push and will update the apps live. This does have an advantage since it will update the UI and logic but what if the content update was only intended for a specific user or set of users?
### Latest version supported
Another approach is to simply have an SLO/Policy that you only support X number of recent releases and that the app will not work on older versions.
For something like this on mobile you would use [upgrader](https://pub.dev/packages/upgrader) via [AppCast](https://sparkle-project.org/about/) or [in\_app\_update](https://pub.dev/packages/in_app_update) to use Google Play APIs to update the app in the background or prevent using until updated.
This has an advantage to know that users will be on the latest or no be supported and allow you to target newer APIs and roll updates easier. This does mean that users will be frustrated by updates more and that older devices may not be supported.
### Server driven updates
Not all apps need to render data on the server and sometimes when building an offline first application you want to do everything local first, but this is for when you need to build a server first application. Here are some examples and use cases:
* Bank accounts
* Airline / Hotel / Car booking
* Chat applications (Instant messaging)
* Marketing and AB testing
* Database first applications
Each of these examples does not mean they are server only and in many cases you want to still cache the data locally to still offer a great offline experience.
With Flutter you are building a runtime that you are shipping to the user as a Single Page Application (SPA) on the web and a mobile/desktop app on the stores. This means you need to ship all the logic and UI for every update.
This has an advantage for doing more logic on the server and potentially really heavy requests are done server side and just the rendered UI is sent to the client. The client can still cache the response and allow for offline viewing too. These disadvantage here is that the client is expected to communicate with the server at some point and may not be suitable for offline only applications.
## Remote Flutter Widgets (RFW)
The Flutter team has a package for creating widgets on the client and server and sending data necessary to connect them. This package is called [rfw](https://pub.dev/packages/rfw).
> While it is possible to ship logic in addition to UI as WASM that is out of scope for this post
The rfw package uses a text format that can be compiled to binary and be used to represent state and dispatch events.
```
import core;
import material;
widget MaterialShop = Scaffold(
appBar: AppBar(
title: Text(text: ['Products']),
),
body: ListView(
children: [
...for product in data.server.games:
Product(product: product)
],
),
);
widget Product = ListTile(
title: Text(text: args.product.name),
onTap: event 'shop.productSelect' { name: args.product.name, path: args.product.link },
);
```
Multiple widgets can be defined and it may even look similar to the Dart API you are used to in Flutter but it is not quite the same.
There are not logic branching blocks or conditional rendering but rather is a stateless format capable of updating every frame if needed.
Take the following Flutter counter app that is generated when you create a new project:
```
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
```
We could represent that in rfw like this:
```
import widgets;
import material;
widget root = Scaffold(
appBar: AppBar(
title: Text(text: ['Counter Example']),
centerTitle: true,
backgroundColor: data.colorScheme.inversePrimary,
),
body: Center(
child: Column(
mainAxisAlignment: "center",
children: [
Text(text: ["You have pushed the button this many times:"]),
Text(
text: [data.counter.value],
style: {
fontSize: 20.0,
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: event "click" {},
tooltip: ["Increment"],
child: Icon(
icon: 0xe047,
fontFamily: 'MaterialIcons',
),
),
);
```
You may have noticed some arrays for strings and different ways of defining widgets. That is by design and the API will not be 100% with the Flutter SDK, but with the limitation comes different tradeoffs.
You can define any custom widgets in your application and only uses the UI you have defined in your design system and the server will only be able to generate UI that you expect.
It is also possible to create all the UI in the text format with rows, columns, containers and more.
## Setting up the server
For this example we will be using [dart\_frog](https://dartfrog.vgv.dev/docs/overview) to create the server application.
In the directory that you created earlier run the following commands:
```
dart_frog create server
flutter pub add rfw
```
This will generate the server boilerplate for us and add the correct dependencies.
> Feel free to delete the test directory for now or update it later to check for the correct response.
Navigate to `/server/routes/index.dart` and update the file with the following:
```
import 'package:dart_frog/dart_frog.dart';
import 'package:rfw/formats.dart';
Response onRequest(RequestContext context) {
var count = context.request.headers['COUNTER_VALUE'] ?? '0';
if (context.request.method == HttpMethod.post) {
count = (int.parse(count) + 1).toString();
}
return Response.bytes(
body: encodeLibraryBlob(parseLibraryFile(template)),
headers: {'COUNTER_VALUE': count},
);
}
const template = '''
import widgets;
import material;
widget root = Scaffold(
appBar: AppBar(
title: Text(text: ['Counter Example']),
centerTitle: true,
backgroundColor: data.colorSchemRelated in Design
contribute
IncludedLocal-only OSS contribution command center. Auto-refreshes the user's in-flight PR and issue state on invoke so conversations start with full context — no need to brief Claude on what's in flight. Helps the user find issues to contribute to on GitHub, builds per-repo dossiers of what each upstream expects (CLA, DCO, branch convention, AI policy, draft-first, review bots, issue templates), runs deterministic gates before any external action so AI-assisted contributions don't reach maintainers as slop. State is markdown-only: candidate files at ~/.contribute-system/candidates/, repo dossiers at ~/.contribute-system/research/, append-only event log at ~/.contribute-system/log.jsonl. No database, no cloud calls. Use when the user asks about their PRs / issues / contributions, wants to find new work to take on, claim an issue, build/refresh a repo's dossier, or draft a Design Issue or PR. Trigger with "/contribute", "what's my PR status", "find a contribution", "claim issue X", "draft a Design Issue for Y", "refresh dossier for Z".
architectural-analysis
IncludedUser-triggered deep architectural analysis of a codebase or scoped subtree across eight modes — information architecture, data flow, integration points, UI surfaces, interaction patterns, data model, control flow, and failure modes. This skill should be used when the user asks to "diagram this codebase," "map the architecture," "show the data flow," "give me an ERD," "trace control flow," "find the integration points," "verify the layout pattern," "audit the UX architecture," or any similar request whose primary deliverable is mermaid diagrams plus cited reports under docs/architecture/. Dispatches haiku/sonnet sub-agents in parallel for per-mode exploration, then verifies every citation mechanically before any node lands in a diagram. Not for one-off prose explanations of code (use code-explanation) or for high-level system design from scratch (use system-design).
mcp
IncludedModel Context Protocol (MCP) server development and tool management. Languages: Python, TypeScript. Capabilities: build MCP servers, integrate external APIs, discover/execute MCP tools, manage multi-server configs, design agent-centric tools. Actions: create, build, integrate, discover, execute, configure MCP servers/tools. Keywords: MCP, Model Context Protocol, MCP server, MCP tool, stdio transport, SSE transport, tool discovery, resource provider, prompt template, external API integration, Gemini CLI MCP, Claude MCP, agent tools, tool execution, server config. Use when: building MCP servers, integrating external APIs as MCP tools, discovering available MCP tools, executing MCP capabilities, configuring multi-server setups, designing tools for AI agents.
react-native-skia
IncludedDesign, build, debug, and optimise high-polish animated graphics in React Native or Expo using @shopify/react-native-skia, Reanimated, and Gesture Handler. Use when the user wants canvas-driven UI, shaders, paths, rich text, image filters, sprite fields, Skottie, video frames, snapshots, web CanvasKit setup, or performance tuning for custom motion-heavy elements such as loaders, hero art, cards, charts, progress indicators, particle systems, or gesture-driven surfaces. Also use when the user asks for fluid, glow, glass, blob, parallax, 60fps/120fps, or GPU-friendly animated effects in React Native, even if they do not explicitly say "Skia". Do not use for ordinary form/layout work with standard views.
plaid
IncludedProduct Led AI Development — guides founders from idea to launched product. Six capabilities: Idea (discover a product idea), Validate (pressure-test the idea against fatal flaws, problem reality, competition, and 2-week MVP feasibility), Plan (vision intake + document generation), Design (translate image references into a design.md spec), Launch (go-to-market strategy), and Build (roadmap execution). Use when someone says "PLAID", "plaid idea", "help me find an idea", "product idea", "idea from my business", "idea from my expertise", "plaid validate", "validate my idea", "pressure-test", "is this idea good", "find fatal flaws", "validate the problem", "plan a product", "define my vision", "generate a PRD", "product strategy", "plaid design", "design from image", "translate image to design", "create design.md", "extract design tokens", "plaid launch", "go-to-market", "launch plan", "GTM strategy", "launch playbook", "plaid build", "build the app", "start building", or "execute the roadmap".
nextjs-framer-motion-animations
IncludedAdds production-safe Motion for React or Framer Motion animations to Next.js apps, including reveal, hover and tap micro-interactions, whileInView, stagger, AnimatePresence, layout and layoutId transitions, reorder, scroll-linked UI, and lightweight route-content transitions. Use when the user asks to add, refactor, or debug Motion or Framer Motion in App Router or Pages Router codebases, especially around server/client boundaries, reduced motion, LazyMotion, bundle size, hydration, or route transitions. Avoid for GSAP-style timelines, WebGL or 3D scenes, heavy scroll storytelling, or CSS-only effects unless Motion is explicitly requested.