Skill v1.0.1
Automated scan100/100+1 new
version: "1.0.1" name: jaspr-pre-rendering-and-hydration description: Use when working in a static or server mode Jaspr project. Covers SSR/SSG, async data fetching, the @client annotation, hydration, and handling dual entrypoints (.server.dart vs .client.dart). metadata: jaspr_version: 0.23.3
This skill is relevant for static and server mode Jaspr projects. You can check the mode of a Jaspr project in pubspec.yaml under jaspr: mode: <mode>.
Core Rules for Pre-rendering (SSR/SSG)
Jaspr pre-renders components at request/build-time on the server. Interactive browser features require hydration.
- Dual Entrypoints:
- Server:
lib/main.server.dart-> CallsrunApp(Document(body: MyApp())) - Client:
lib/main.client.dart-> CallsrunApp(ClientApp())
- Library Constraints:
- Importing: You MUST NOT import
dart:js_interoporpackage:webin any code imported by the server entrypoint. - Importing: You MUST NOT import
dart:ioordart:ffiin any code imported by the client entrypoint. - Using: You can import
package:universal_webin shared code, but you MUST wrap any access to its browser APIs inside anif (kIsWeb)check to prevent crashing during server-side rendering.
- Interactivity / Hydration: Components are server-rendered by default. To add client interactivity (e.g., event listeners, state, using browser APIs), you MUST annotate a component with
@client.
The @client Annotation and ClientApp
When you annotate a component with @client, Jaspr renders its initial HTML on the server and "hydrates" it in the browser.
ClientApp()(called in the client entrypoint) automatically looks for and hydrates all@clientcomponents that were pre-rendered on the server.- Rule: Only the uppermost component of a subtree that needs client interactivity needs to be annotated with
@client. Do not annotate child components in that subtree, they will automatically be hydrated as part of the parent.
Passing Data to @client Components
Use parameters of @client components to pass data from the server to the client. These are automatically serialized on the server and deserialized in the browser.
- Rule 1: All parameters MUST be initializing fields (e.g.,
const MyClientComponent({required this.title});). - Rule 2: All parameters MUST be serializable. They must be primitive types (
int,String,double,bool,List,Map) OR custom types that define@encoderand@decodermethods. - Parameters MUST NOT be functions, components, or any other non-serializable types.
Fetching Data on the Server (Async Components)
When pre-rendering in server or static mode, you often need to fetch data asynchronously (e.g., from a database or API) before rendering the HTML. Jaspr provides special server-only components to delay the build phase until the data is ready.
- Rule 1: You MUST use
AsyncStatelessComponentorAsyncBuilderto perform async work during the build phase. - Rule 2: These components MUST ONLY be used on the server (import
package:jaspr/server.dart). - Rule 3: Once the data is fetched on the server, you should pass it to a
@clientcomponent as a parameter if you need that data to be available for client-side interactivity.
Example Usage:
import 'package:jaspr/server.dart'; // Note the server-only import!import 'package:jaspr/dom.dart';import 'my_interactive_button.dart';// This component fetches data before server-rendering the HTML.class MyServerComponent extends AsyncStatelessComponent {const MyServerComponent();@overrideFuture<Component> build(BuildContext context) async {// 1. Fetch data asynchronously on the serverfinal databaseCount = await loadCountFromDatabase();return div([.text('This is static.'),// 2. Pass the fetched data to the client componentMyInteractiveButton(initialCount: databaseCount),]);}}
import 'package:jaspr/jaspr.dart';import 'package:jaspr/dom.dart';// This component runs in the browser (interactive).@clientclass MyInteractiveButton extends StatefulComponent {const MyInteractiveButton({this.initialCount = 0});final int initialCount;@overrideState<MyInteractiveButton> createState() => _MyInteractiveButtonState();}class _MyInteractiveButtonState extends State<MyInteractiveButton> {late int count = component.initialCount;@overrideComponent build(BuildContext context) {return button(onClick: () => setState(() => count++),[.text('Clicked $count times')],);}}