Skip to content

[WIP] Server Components#776

Draft
schultek wants to merge 30 commits into
nextfrom
feat/server-components
Draft

[WIP] Server Components#776
schultek wants to merge 30 commits into
nextfrom
feat/server-components

Conversation

@schultek

Copy link
Copy Markdown
Owner

Description

Add support for Server Components to Jaspr.

Type of Change

  • ❌ Breaking change
  • ✨ New feature or improvement

Kilian Schulte and others added 21 commits June 14, 2024 16:39
# Conflicts:
#	packages/jaspr/lib/src/browser/browser_binding.dart
#	packages/jaspr/lib/src/browser/clients.dart
#	packages/jaspr/lib/src/browser/js_data.dart
#	packages/jaspr/lib/src/foundation/sync.dart
#	packages/jaspr/lib/src/framework/render_element.dart
#	packages/jaspr/lib/src/server/document/document.dart
#	packages/jaspr/lib/src/server/document/document_base.dart
#	packages/jaspr/lib/src/server/document/document_binding.dart
#	packages/jaspr/lib/src/server/document/document_file.dart
#	packages/jaspr/lib/src/server/markup_renderer.dart
#	packages/jaspr_builder/lib/src/client/client_part_builder.dart
# Conflicts:
#	packages/jaspr/lib/src/browser/clients.dart
#	packages/jaspr_builder/lib/src/client/client_module_builder.dart
# Conflicts:
#	packages/jaspr/lib/browser.dart
#	packages/jaspr/lib/src/browser/clients.dart
#	packages/jaspr/lib/src/components/raw_text/raw_text_web.dart
#	packages/jaspr/lib/src/foundation/options.dart
#	packages/jaspr/lib/src/server/adapters/client_component_adapter.dart
#	packages/jaspr/lib/src/server/adapters/client_script_adapter.dart
#	packages/jaspr/lib/src/server/adapters/sync_script_adapter.dart
#	packages/jaspr/lib/src/server/server_binding.dart
#	packages/jaspr_builder/lib/src/client/client_module_builder.dart
#	packages/jaspr_builder/lib/src/client/client_registry_builder.dart
# Conflicts:
#	packages/jaspr/lib/src/browser/clients.dart
#	packages/jaspr/lib/src/components/raw_text/raw_text_web.dart
#	packages/jaspr/lib/src/foundation/marker_utils.dart
#	packages/jaspr/lib/src/foundation/options.dart
#	packages/jaspr/lib/src/server/adapters/client_component_adapter.dart
#	packages/jaspr/lib/src/server/adapters/client_script_adapter.dart
#	packages/jaspr/lib/src/server/server_binding.dart
#	packages/jaspr/test/browser/hydration/hydration_browser_test.dart
#	packages/jaspr_builder/lib/src/client/client_module_builder.dart
#	packages/jaspr_builder/lib/src/codec/codecs.dart
# Conflicts:
#	packages/jaspr/lib/src/browser/clients.dart
#	packages/jaspr/lib/src/client/custom_node_component.dart
#	packages/jaspr/lib/src/dom/raw_text/raw_text_web.dart
#	packages/jaspr/lib/src/foundation/options.dart
#	packages/jaspr/lib/src/server/adapters/client_component_adapter.dart
#	packages/jaspr/lib/src/server/adapters/client_script_adapter.dart
#	packages/jaspr/lib/src/server/server_binding.dart
#	packages/jaspr/test/client/hydration/hydration_browser_test.dart
#	packages/jaspr_builder/lib/src/client/client_module_builder.dart
#	packages/jaspr_builder/lib/src/codec/codec_module_builder.dart
#	packages/jaspr_builder/test/client/sources/client_basic.dart
#	packages/jaspr_builder/test/client/sources/client_model_class.dart
#	packages/jaspr_builder/test/client/sources/client_model_extension.dart
#	packages/jaspr_builder/test/sync/sources/sync_model_class.dart
#	packages/jaspr_builder/test/sync/sources/sync_model_extension.dart
# Conflicts:
#	packages/jaspr/lib/src/client/component_anchors.dart
#	packages/jaspr/lib/src/server/adapters/client_component_adapter.dart
#	packages/jaspr/lib/src/server/server_binding.dart
#	packages/jaspr_builder/lib/src/client/client_module_builder.dart
#	packages/jaspr_builder/lib/src/codec/codecs.dart
#	packages/jaspr_builder/lib/src/sync/sync_mixins_builder.dart
#	packages/jaspr_builder/test/client/sources/bundle.dart
#	packages/jaspr_builder/test/client/sources/client_basic.dart
#	packages/jaspr_builder/test/client/sources/client_model_class.dart
#	packages/jaspr_builder/test/client/sources/client_model_extension.dart
#	packages/jaspr_builder/test/codec/sources/bundle.dart
#	packages/jaspr_builder/test/codec/sources/model_class.dart
#	packages/jaspr_builder/test/codec/sources/model_extension.dart
#	packages/jaspr_builder/test/sync/sources/sync_basic.dart
#	packages/jaspr_builder/test/sync/sources/sync_multi.dart
@docs-page

docs-page Bot commented Mar 16, 2026

Copy link
Copy Markdown

To view this pull requests documentation preview, visit the following URL:

docs.page/schultek/jaspr~776

Documentation is deployed and generated using docs.page.

@github-actions

github-actions Bot commented Mar 16, 2026

Copy link
Copy Markdown

Package Version Report

The following packages have been updated:
jaspr_test : 0.23.1 -> 0.24.0
jaspr : 0.23.1 -> 0.24.0
jaspr_builder : 0.23.1 -> 0.24.0
jaspr_cli : 0.23.1 -> 0.24.0
jaspr_router : 0.8.2 -> 0.8.3
jaspr_content : 0.5.2 -> 0.5.3
jaspr_flutter_embed : 0.4.11 -> 0.4.12
jaspr_riverpod : 0.4.5 -> 0.4.6
jaspr_serverpod : 0.6.1 -> 0.6.2

@codecov

codecov Bot commented Mar 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.35943% with 116 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.51%. Comparing base (6148d07) to head (1ad049e).
⚠️ Report is 1 commits behind head on next.

Files with missing lines Patch % Lines
...kages/jaspr/lib/src/client/slotted_child_view.dart 72.00% 28 Missing ⚠️
packages/jaspr/lib/src/client/client_binding.dart 36.36% 21 Missing ⚠️
packages/jaspr/lib/src/server/server_handler.dart 15.00% 17 Missing ⚠️
...ckages/jaspr/lib/src/client/component_anchors.dart 87.17% 10 Missing ⚠️
packages/jaspr/lib/src/framework/components.dart 90.81% 9 Missing ⚠️
packages/jaspr/lib/src/server/server_app.dart 0.00% 5 Missing ⚠️
packages/jaspr/lib/src/server/server_binding.dart 61.53% 5 Missing ⚠️
.../src/server/adapters/client_component_adapter.dart 87.09% 4 Missing ⚠️
packages/jaspr_builder/lib/src/codec/codecs.dart 90.00% 3 Missing ⚠️
packages/jaspr/lib/src/client/options.dart 85.71% 2 Missing ⚠️
... and 9 more
Additional details and impacted files
@@            Coverage Diff             @@
##             next     #776      +/-   ##
==========================================
+ Coverage   68.56%   69.51%   +0.94%     
==========================================
  Files         212      215       +3     
  Lines       11867    12311     +444     
==========================================
+ Hits         8137     8558     +421     
- Misses       3730     3753      +23     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@parlough parlough left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exciting!! I haven't tried it out yet, but some small questions and a few minor suggestions to consider:

Comment thread packages/jaspr/lib/src/client/component_anchors.dart Outdated
Comment thread packages/jaspr/lib/src/client/component_anchors.dart Outdated
Comment thread packages/jaspr/lib/src/client/options.dart Outdated
Comment thread packages/jaspr/lib/src/client/options.dart Outdated
Comment thread packages/jaspr/lib/src/client/options.dart Outdated
Comment thread packages/jaspr/lib/src/server/server_binding.dart
Comment thread packages/jaspr/lib/src/server/server_binding.dart Outdated
Comment thread packages/jaspr/lib/src/client/client_binding.dart Outdated
Comment thread packages/jaspr/lib/src/stub/app_context_stub.dart Outdated
Comment thread packages/jaspr_builder/lib/src/codec/codecs.dart
@schultek schultek force-pushed the feat/server-components branch from 38c987f to e46ec38 Compare March 18, 2026 14:11
@schultek schultek changed the base branch from main to next April 20, 2026 09:38
Comment thread packages/jaspr/lib/src/client/slotted_child_view.dart

@parlough parlough left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still love it. Some small fixes, a few suggestions, and a question but otherwise I think it's in a great place for developers to start trying it out.

///
const factory Component.wrapElement({
Key? key,
const factory Component.apply({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the ComponentFactoryMigration be updated to use this new factory instead?

if (wrapDomComponents.isNotEmpty) {
context.reporter.createMigration('Replaced DomComponent.wrap() with Component.wrapElement()', (builder) {
for (final node in wrapDomComponents) {
builder.replace(node.offset, node.length, 'Component.wrapElement');
}
});
}

import 'components/expandable_card.dart';

void main() {
Jaspr.initializeAll(options: defaultServerOptions);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be initializeApp?


You can use `@client` components normally as any other component.

To have separate interactive parts of your site, simply annotate multiple components with `@client`. Only the outermost `@client` component of a nested sub-tree will be hydrated on the client, while nested client components will be safely ignored.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prose needs a slight update to account for server components in the sub tree.


When building modern web applications, we want both rich, fluid interactivity and fast initial page loads.

Typically, once you make a parent component interactive by decorating it with `@client`, Jaspr must compile it and **all of its nested children** to JavaScript so they can run in the browser. Without Server Components, the moment a layout or parent container needs some client-side interactivity, every single child in its subtree gets pulled into the client-side JavaScript bundle—even if those children are completely static.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adjusting this to account for possibility of compiling to Wasm.

Suggested change
Typically, once you make a parent component interactive by decorating it with `@client`, Jaspr must compile it and **all of its nested children** to JavaScript so they can run in the browser. Without Server Components, the moment a layout or parent container needs some client-side interactivity, every single child in its subtree gets pulled into the client-side JavaScript bundle—even if those children are completely static.
Typically, once you make a parent component interactive by decorating it with `@client`, Jaspr must compile it and **all of its nested children** so they can run in the browser. Without Server Components, the moment a layout or parent container needs some client-side interactivity, every single child in its subtree gets pulled into the client-side bundle—even if those children are completely static.

**Server Components** solve this by enabling **surgical hydration**. They allow you to render server-side-only components inside parts of an interactive, hydrated client-side application. With this pattern, you can build interactive client-side wrappers (like expandable cards, navigation shells, sidebars, or tabs) while completely skipping compiling or shipping the static or heavy content inside them.

As a result:
- **Reduced Bundle Size:** The Dart code for server-only components is never compiled to JavaScript or shipped to the browser, significantly reducing the application's JavaScript footprint.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Reduced Bundle Size:** The Dart code for server-only components is never compiled to JavaScript or shipped to the browser, significantly reducing the application's JavaScript footprint.
- **Reduced Bundle Size:** The Dart code for server-only components is never compiled to JavaScript/Wasm or shipped to the browser, significantly reducing the application's client-side footprint.


Instead, Server Components are a design paradigm enabled by Jaspr's compilation and hydration model. You achieve this simply by **passing components as parameters** (such as a `child` component, or lists/maps of components) to a component annotated with [`@client`](/api/utils/at_client).

## A Simple Example: Expandable Card

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of building something to show the feature. It works well here and is the easiest way to build an understanding of it.

Here is the interactive card component:

```dart title="lib/components/expandable_card.dart"
import 'package:jaspr/jaspr.dart';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import 'package:jaspr/jaspr.dart';
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';

Comment on lines +224 to +227
// WARNING: If showContent is false during SSR, 'widget.content' is not built on the server.
// When showContent becomes true on the client, it will render as EMPTY because
// the client does not have the Dart code to construct the server component.
if (showContent) widget.content,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// WARNING: If showContent is false during SSR, 'widget.content' is not built on the server.
// When showContent becomes true on the client, it will render as EMPTY because
// the client does not have the Dart code to construct the server component.
if (showContent) widget.content,
// WARNING: If showContent is false during SSR, 'component.content' is not built on the server.
// When showContent becomes true on the client, it will render as EMPTY because
// the client does not have the Dart code to construct the server component.
if (showContent) component.content,

@override
Component build(BuildContext context) {
return div([
button(onClick: () => setState(() => showContent = true), [Text('Reveal')]),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
button(onClick: () => setState(() => showContent = true), [Text('Reveal')]),
button(onClick: () => setState(() => showContent = true), [.text('Reveal')]),

@override
void willRebuildElement(Element element) {}
String getDataForServerComponent(Component component, Element parent) {
if (registry.serverComponents[component] case final s?) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this safe? Say if two different elements on the page are from the same component but with different data? Do they need their own marker?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants