Server-side rendering (SSR) generates HTML on the server for each incoming request. This provides excellent SEO, fast initial page loads, and support for dynamic content.
Configuration
To use server-side rendering, set the mode in jaspr_options.yaml:
This tells Jaspr to generate a server executable that handles HTTP requests and renders pages dynamically.
Server Entry Point
Create a server entry point file (typically lib/main.server.dart):
lib/main.server.dart
lib/app.dart
import 'package:jaspr/server.dart' ;
void main () {
Jaspr . initializeApp (
options : defaultServerOptions,
);
runApp ( App ());
}
Initialization Options
The Jaspr.initializeApp() method accepts several configuration options:
Jaspr . initializeApp (
// Generated options containing client component configuration
options : defaultServerOptions,
// Use isolates for rendering (better performance under load)
useIsolates : false ,
// Which file extensions are allowed to be rendered
allowedPathSuffixes : [ 'html' , 'htm' , 'xml' ],
);
Server Options
The ServerOptions class (auto-generated) contains:
clientId : Unique identifier for the client bundle
clients : Map of client component types to their targets
styles : Function returning global styles
You should use the generated defaultServerOptions instead of creating ServerOptions manually. This is generated by the Jaspr build system.
Running the Server
Development
Start a development server with hot-reload:
This starts the server and watches for file changes.
Production Build
Build a production executable:
This creates:
build/jaspr/app - The server executable
build/jaspr/web/ - Static assets (JS, CSS, images)
Deployment
Run the built executable:
The server listens on the port specified by the PORT environment variable (default: 8080):
PORT = 3000 ./build/jaspr/app
Advanced Server Usage
Custom Request Handling
For advanced use cases, use serveApp() to create a custom Shelf handler:
import 'package:jaspr/server.dart' ;
import 'package:shelf/shelf.dart' ;
import 'package:shelf/shelf_io.dart' as shelf_io;
void main () async {
Jaspr . initializeApp (options : defaultServerOptions);
final handler = serveApp ((request, render) async {
// Custom request handling logic
final path = request.url.path;
if (path == '/api/data' ) {
// Return JSON response
return Response . ok (
'{"message": "Hello from API"}' ,
headers : { 'Content-Type' : 'application/json' },
);
}
// Render component for other routes
return render ( App ());
});
final server = await shelf_io. serve (handler, 'localhost' , 8080 );
print ( 'Server listening on port ${ server . port } ' );
}
Direct Component Rendering
Render a component directly to HTML without starting a server:
import 'package:jaspr/server.dart' ;
Future < void > main () async {
Jaspr . initializeApp (options : defaultServerOptions);
final response = await renderComponent (
MyComponent (),
standalone : true , // Don't include <html>, <head>, <body>
);
print ( 'Status: ${ response . statusCode } ' );
print ( 'Body: ${ String . fromCharCodes ( response . body )} ' );
print ( 'Headers: ${ response . headers } ' );
}
Server-Only Components
Some components and features are only available on the server:
AsyncStatelessComponent
Components that perform async operations during build:
import 'package:jaspr/server.dart' ;
class UserProfile extends AsyncStatelessComponent {
const UserProfile ({ required this .userId});
final String userId;
@override
Future < Component > build ( BuildContext context) async {
// Fetch user data asynchronously
final user = await fetchUserFromDatabase (userId);
return div ([
h1 ([. text (user.name)]),
p ([. text (user.email)]),
img (src : user.avatar),
]);
}
}
AsyncBuilder
For inline async building:
import 'package:jaspr/server.dart' ;
AsyncBuilder (
builder : (context) async {
final data = await fetchData ();
return div ([. text ( 'Data: $ data ' )]);
},
)
Async components are only available on the server . They will throw an error if used in client code or in static rendering without data preloading.
Document Structure
Control the HTML document structure with the Document component:
Basic Document
Template Document
Dynamic Head
Document (
title : 'My Page' ,
lang : 'en' ,
base : '/' ,
charset : 'utf-8' ,
viewport : 'width=device-width, initial-scale=1.0' ,
meta : {
'description' : 'My awesome page' ,
'keywords' : 'jaspr, dart, ssr' ,
},
head : [
link (rel : 'stylesheet' , href : '/styles.css' ),
script (src : '/analytics.js' , []),
],
body : HomePage (),
)
Document . template (
name : 'index' , // Loads web/index.template.html
attachTo : 'body' , // Where to attach the component
child : App (),
)
Create web/index.template.html: <! DOCTYPE html >
< html >
< head >
< title > My App </ title >
< link rel = "stylesheet" href = "/custom.css" >
</ head >
< body >
<!-- App will be attached here -->
</ body >
</ html >
class ProductPage extends StatelessComponent {
const ProductPage ({ required this .product});
final Product product;
@override
Component build ( BuildContext context) {
return div ([
Document . head (
title : product.name,
meta : {
'description' : product.description,
'og:image' : product.imageUrl,
},
),
h1 ([. text (product.name)]),
// ... rest of the page
]);
}
}
Use Isolates
Enable isolate-based rendering for better performance under load:
Jaspr . initializeApp (
options : defaultServerOptions,
useIsolates : true , // Each request renders in a separate isolate
);
This isolates rendering work, preventing slow requests from blocking others.
Isolates add overhead for each request. Only enable this for high-traffic applications where concurrency is critical.
Middleware
Add Shelf middleware for caching, compression, and more:
import 'package:shelf/shelf.dart' ;
import 'package:shelf_gzip/shelf_gzip.dart' ;
import 'package:jaspr/server.dart' ;
void main () {
ServerApp . addMiddleware ( gzipMiddleware ());
ServerApp . addMiddleware ( logRequests ());
Jaspr . initializeApp (options : defaultServerOptions);
runApp ( App ());
}
Conditional Rendering
Render different content based on the request:
class App extends StatelessComponent {
@override
Component build ( BuildContext context) {
final url = context.binding.currentUrl;
final isMobile = context.request ? .headers[ 'user-agent' ]
? . contains ( 'Mobile' ) ?? false ;
return div ([
if (isMobile) MobileLayout () else DesktopLayout (),
]);
}
}
API Reference
runApp()
Starts the server application:
void runApp ( Component app)
Source: packages/jaspr/lib/src/server/run_app.dart:15
serveApp()
Creates a Shelf handler for custom server logic:
Handler serveApp ( AppHandler handler)
Source: packages/jaspr/lib/src/server/run_app.dart:24
renderComponent()
Directly renders a component to HTML:
Future < ResponseLike > renderComponent (
Component app,
{ Request ? request, bool standalone = false }
)
Source: packages/jaspr/lib/src/server/run_app.dart:40
Next Steps
Client-Side Rendering Learn how to add interactivity with client components
Static Site Generation Pre-render pages for maximum performance