`\n\n2. The entrypoint on the server will:\n - be the file specified using the `-i (--input)` option when running `jaspr serve` or `jaspr build` or\n - default to `lib/main.dart`\n\n## ๐Ÿ’ฝ Loading Data on the Server\n\nWhen using server side rendering, you want the ability to load some data data before rendering the html.\nWith `jaspr` this is build into the package and easy to do.\n\nStart by using the `PreloadStateMixin` on a `StatefulComponent`s `State` class and implement the `Future preloadState()` method.\n\n```dart\nclass MyState extends State with PreloadStateMixin {\n \n @override\n Future preloadState() async {\n ...\n }\n}\n```\n\nThis method will only be executed on the server and will be called before `initState()`. It will defer\n`initState()` as well as building the component until the future returned by the method is completed.\n\n\nWhile it is only executed on the server it still will be compiled for the client, since Dart does not have\nselective compilation. So you have to make sure it will compile on the client, e.g. by using service locators\nand providing mock services on the client. (**TODO**: More explanation and How To)\n\n\n\nThis is a great use-case for jasprs [platform-specific imports](/advanced/imports)\n\n\n## โ™ป๏ธ Syncing Data to the Client\n\nAlongside with preloading some data on the server, you often want to also sync the data with the client.\nYou can simply add the `SyncStateMixin` which will automatically sync state from the server with the client.\n\nThe `SyncStateMixin` accepts a second type argument for the data type that you want to sync. You the have to\nimplement the the `getState` and `updateState()` methods.\n\n```dart\nclass MyState extends State with SyncStateMixin {\n\n MyStateModel? model;\n \n // a globally unique id that is used to identify the state\n @override\n String get syncId => 'my_id';\n\n // this will get the state to be sent to the client\n // and is only executed on the server\n @override\n MyStateModel? getState() {\n return model;\n }\n\n // this will receive the state on the client\n // and it is safe to call setState\n void updateState(MyStateModel? value) {\n setState(() {\n model = value;\n });\n }\n}\n```\n\nIn order to send the data, it needs to be serialized on the server and deserialized on the client.\nThe serialization format is defined by the `syncCodec` getter defined in `SyncStateMixin`.\n\nThe codec must encode to a primitive or object value of one of the supported types: `Null, bool,\ndouble, int, Uint8List, String, Map, List`. By default, the codec is null and your state must\nalready be one of the supported types.\n\nWhen you want to use another type like a custom model class, you have to override the `syncCodec` getter, which\nhas to return a `Codec` that encodes to `String`. This can be any codec, however a typical use would be to\nencode your model to `Map` and the fuse this with the json codec to get a json string.\n\n```dart\n\nclass MyState extends State with SyncStateMixin {\n\n @override\n Codec get syncCodec => MyStateModelCodec();\n\n ...\n}\n\n// codec that encodes a value of MyStateModel to Map\nclass MyStateModelCodec extends Codec> {\n \n ...\n \n}\n```\n\n## ๐Ÿ”€ Integrate with other backend framework\n\nWhen you use `runApp(App())` on the server-side, jaspr spins up its own http server to serve all\nincoming requests. However depending on your app, you may want to add your own request handling and\nserver setup, e.g. if you want to add an api to your application.\n\nJaspr provides two ways to control the server setup:\n\n#### 1. Extend the default request handling by adding middleware to `ServerApp`.\n\nOn the server, the `runApp()` function will actually return an instance of `ServerApp` where you can\nextend the server and request handling of your application.\nTo make this available, change your jaspr import to `import 'package:jaspr/jaspr_server.dart';`. Then\nchain some calls after the `runApp()` function, e.g. `runApp(App()).addMiddleware(someShelfMiddleware)`.\nJaspr uses `package:shelf` for this to be compatible with most common dart backends. You can also:\n\n- add a listener using `.setListener((server) { ... })`.\n This will run the callback when the http server first started listening for requests, and then\n each time the app is reloaded through auto-reload.\n\n- add a builder using `.setBuilder((handler) => myHttpServerImpl(handler))`.\n A builder has to return a `HttpServer`, which will completely replace the default server created\n by jaspr.\n\n- check out [the server_handling example](https://github.com/schultek/jaspr/tree/main/examples/custom_server_middleware) to see this in action.\n\n\n#### 2. Add jaspr to any existing backend implementation / framework as a **shelf handler**.\n\nInstead of letting jaspr run your app and manage your http server, you can also use your favourite\nbackend package or framework, and simply add jaspr as a request handler to your implementation.\n\nTo do this, remove the `runApp()` call and setup your backend as you normally would. When defining your\nroute handlers, create a handler for jaspr by using `serveApp` like this:\n```dart\nvar handler = serveApp((request, render) {\n// Optionally do something with `request`\nprint(\"Request uri is ${request.requestedUri}\");\n// Return a server-rendered response by calling `render()` with your root component\nreturn render(App());\n});\n\n// provide `handler` to your app, e.g.\nawait shelf_io.serve(handler, InternetAddress.anyIPv4, 8080);\n```\n\nCheck out [the shelf_handler example](https://github.com/schultek/jaspr/tree/main/examples/shelf_backend) to see this in action.\n\n**Be aware** that using the `serveApp` handler does not work with auto-reload. When building a simple\nshelf backend, it is better to use `runApp()` with a custom `.setBuilder()` to keep this functionality.\n\n**Make sure** that you set the base path of your app when mounting the `serveApp` handler to\nany other route prefix than `/`, otherwise your static resources like `styles.css` or `main.dart.js`\ncan't be loaded correctly. Add the `/\">` tag to the `head` of your\n`index.html`, as demonstrated in the example.","headings":[{"id":"-loading-data-on-the-server","title":"๐Ÿ’ฝ Loading Data on the Server","rank":2},{"id":"๏ธ-syncing-data-to-the-client","title":"โ™ป๏ธ Syncing Data to the Client","rank":2},{"id":"-integrate-with-other-backend-framework","title":"๐Ÿ”€ Integrate with other backend framework","rank":2}],"frontmatter":{"title":"Jaspr Server-Side Rendering","description":"How to setup server-side rendering and sync data between server and client in Jaspr.","previous":"/core/styling","next":"/core/routing"},"code":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n section: \"section\",\n h1: \"h1\",\n p: \"p\",\n strong: \"strong\",\n ol: \"ol\",\n li: \"li\",\n code: \"code\",\n ul: \"ul\",\n h2: \"h2\",\n span: \"span\",\n pre: \"pre\",\n a: \"a\",\n h4: \"h4\"\n }, props.components), {Warning, Info} = _components;\n if (!Info) _missingMdxReference(\"Info\", true);\n if (!Warning) _missingMdxReference(\"Warning\", true);\n return _jsxs(_Fragment, {\n children: [_jsxs(_components.section, {\n id: \"-server-side-rendering\",\n children: [_jsx(_components.h1, {\n id: \"-server-side-rendering\",\n children: \"๐Ÿ— Server Side Rendering\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Jaspr\"\n }), \" is built to be server-side-rendering first. It leverages the power of Darts\\ncross-compilation to both js and native executables to execute the same app on the server and client.\"]\n }), \"\\n\", _jsx(_components.p, {\n children: \"For each target (server and client) you need to have a separate entrypoint to start your app. However\\ndepending on what project setup you choose, the web entrypoint might be automatically generated for you.\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [\"The entrypoint on the web will always be the corresponding file inside the \", _jsx(_components.code, {\n children: \"web\"\n }), \" directory,\\nusually \", _jsx(_components.code, {\n children: \"web/main.dart\"\n }), \". This file will be compiled to js and is included in the \", _jsx(_components.code, {\n children: \"index.html\"\n }), \" file\\nas a script: \", _jsx(_components.code, {\n children: \"\"\n })]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"The entrypoint on the server will:\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"be the file specified using the \", _jsx(_components.code, {\n children: \"-i (--input)\"\n }), \" option when running \", _jsx(_components.code, {\n children: \"jaspr serve\"\n }), \" or \", _jsx(_components.code, {\n children: \"jaspr build\"\n }), \" or\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"default to \", _jsx(_components.code, {\n children: \"lib/main.dart\"\n })]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), _jsxs(_components.section, {\n id: \"-loading-data-on-the-server\",\n children: [_jsxs(_components.h2, {\n id: \"-loading-data-on-the-server\",\n children: [_jsx(_components.span, {\n role: \"img\",\n \"aria-label\": \"computer disk\",\n children: \"๐Ÿ’ฝ\"\n }), \" Loading Data on the Server\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"When using server side rendering, you want the ability to load some data data before rendering the html.\\nWith \", _jsx(_components.code, {\n children: \"jaspr\"\n }), \" this is build into the package and easy to do.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Start by using the \", _jsx(_components.code, {\n children: \"PreloadStateMixin\"\n }), \" on a \", _jsx(_components.code, {\n children: \"StatefulComponent\"\n }), \"s \", _jsx(_components.code, {\n children: \"State\"\n }), \" class and implement the \", _jsx(_components.code, {\n children: \"Future preloadState()\"\n }), \" method.\"]\n }), \"\\n\", _jsx(_components.pre, {\n raw: \"class MyState extends State with PreloadStateMixin {\\n \\n @override\\n Future preloadState() async {\\n ...\\n }\\n}\\n\",\n language: \"dart\",\n html: \"
class MyState extends State<MyStatefulComponent> with PreloadStateMixin {\\n  \\n  @override\\n  Future<void> preloadState() async {\\n    ...\\n  }\\n}\\n
\",\n children: _jsx(_components.code, {\n className: \"language-dart\",\n children: \"class MyState extends State with PreloadStateMixin {\\n \\n @override\\n Future preloadState() async {\\n ...\\n }\\n}\\n\"\n })\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"This method will only be executed on the server and will be called before \", _jsx(_components.code, {\n children: \"initState()\"\n }), \". It will defer\\n\", _jsx(_components.code, {\n children: \"initState()\"\n }), \" as well as building the component until the future returned by the method is completed.\"]\n }), \"\\n\", _jsx(Warning, {\n children: _jsxs(_components.p, {\n children: [\"While it is only executed on the server it still will be compiled for the client, since Dart does not have\\nselective compilation. So you have to make sure it will compile on the client, e.g. by using service locators\\nand providing mock services on the client. (\", _jsx(_components.strong, {\n children: \"TODO\"\n }), \": More explanation and How To)\"]\n })\n }), \"\\n\", _jsx(Info, {\n children: _jsxs(_components.p, {\n children: [\"This is a great use-case for jasprs \", _jsx(_components.a, {\n href: \"/advanced/imports\",\n children: \"platform-specific imports\"\n })]\n })\n }), \"\\n\"]\n }), _jsxs(_components.section, {\n id: \"๏ธ-syncing-data-to-the-client\",\n children: [_jsxs(_components.h2, {\n id: \"๏ธ-syncing-data-to-the-client\",\n children: [_jsx(_components.span, {\n role: \"img\",\n \"aria-label\": \"recycling symbol\",\n children: \"โ™ป๏ธ\"\n }), \" Syncing Data to the Client\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Alongside with preloading some data on the server, you often want to also sync the data with the client.\\nYou can simply add the \", _jsx(_components.code, {\n children: \"SyncStateMixin\"\n }), \" which will automatically sync state from the server with the client.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"The \", _jsx(_components.code, {\n children: \"SyncStateMixin\"\n }), \" accepts a second type argument for the data type that you want to sync. You the have to\\nimplement the the \", _jsx(_components.code, {\n children: \"getState\"\n }), \" and \", _jsx(_components.code, {\n children: \"updateState()\"\n }), \" methods.\"]\n }), \"\\n\", _jsx(_components.pre, {\n raw: \"class MyState extends State with SyncStateMixin {\\n\\n MyStateModel? model;\\n \\n // a globally unique id that is used to identify the state\\n @override\\n String get syncId => 'my_id';\\n\\n // this will get the state to be sent to the client\\n // and is only executed on the server\\n @override\\n MyStateModel? getState() {\\n return model;\\n }\\n\\n // this will receive the state on the client\\n // and it is safe to call setState\\n void updateState(MyStateModel? value) {\\n setState(() {\\n model = value;\\n });\\n }\\n}\\n\",\n language: \"dart\",\n html: \"
class MyState extends State<MyStatefulComponent> with SyncStateMixin<MyStatefulComponent, MyStateModel> {\\n\\n  MyStateModel? model;\\n  \\n  // a globally unique id that is used to identify the state\\n  @override\\n  String get syncId => 'my_id';\\n\\n  // this will get the state to be sent to the client\\n  // and is only executed on the server\\n  @override\\n  MyStateModel? getState() {\\n    return model;\\n  }\\n\\n  // this will receive the state on the client\\n  // and it is safe to call setState\\n  void updateState(MyStateModel? value) {\\n    setState(() {\\n      model = value;\\n    });\\n  }\\n}\\n
\",\n children: _jsx(_components.code, {\n className: \"language-dart\",\n children: \"class MyState extends State with SyncStateMixin {\\n\\n MyStateModel? model;\\n \\n // a globally unique id that is used to identify the state\\n @override\\n String get syncId => 'my_id';\\n\\n // this will get the state to be sent to the client\\n // and is only executed on the server\\n @override\\n MyStateModel? getState() {\\n return model;\\n }\\n\\n // this will receive the state on the client\\n // and it is safe to call setState\\n void updateState(MyStateModel? value) {\\n setState(() {\\n model = value;\\n });\\n }\\n}\\n\"\n })\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"In order to send the data, it needs to be serialized on the server and deserialized on the client.\\nThe serialization format is defined by the \", _jsx(_components.code, {\n children: \"syncCodec\"\n }), \" getter defined in \", _jsx(_components.code, {\n children: \"SyncStateMixin\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"The codec must encode to a primitive or object value of one of the supported types: \", _jsx(_components.code, {\n children: \"Null, bool, double, int, Uint8List, String, Map, List\"\n }), \". By default, the codec is null and your state must\\nalready be one of the supported types.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"When you want to use another type like a custom model class, you have to override the \", _jsx(_components.code, {\n children: \"syncCodec\"\n }), \" getter, which\\nhas to return a \", _jsx(_components.code, {\n children: \"Codec\"\n }), \" that encodes to \", _jsx(_components.code, {\n children: \"String\"\n }), \". This can be any codec, however a typical use would be to\\nencode your model to \", _jsx(_components.code, {\n children: \"Map\"\n }), \" and the fuse this with the json codec to get a json string.\"]\n }), \"\\n\", _jsx(_components.pre, {\n raw: \"\\nclass MyState extends State with SyncStateMixin {\\n\\n @override\\n Codec get syncCodec => MyStateModelCodec();\\n\\n ...\\n}\\n\\n// codec that encodes a value of MyStateModel to Map\\nclass MyStateModelCodec extends Codec> {\\n \\n ...\\n \\n}\\n\",\n language: \"dart\",\n html: \"
\\nclass MyState extends State<MyStatefulComponent> with SyncStateMixin<MyStatefulComponent, MyStateModel> {\\n\\n  @override\\n  Codec<MyStateModel, String> get syncCodec => MyStateModelCodec();\\n\\n  ...\\n}\\n\\n// codec that encodes a value of MyStateModel to Map<String, dynamic>\\nclass MyStateModelCodec extends Codec<MyStateModel, Map<String, dynamic>> {\\n  \\n  ...\\n  \\n}\\n
\",\n children: _jsx(_components.code, {\n className: \"language-dart\",\n children: \"\\nclass MyState extends State with SyncStateMixin {\\n\\n @override\\n Codec get syncCodec => MyStateModelCodec();\\n\\n ...\\n}\\n\\n// codec that encodes a value of MyStateModel to Map\\nclass MyStateModelCodec extends Codec> {\\n \\n ...\\n \\n}\\n\"\n })\n }), \"\\n\"]\n }), _jsxs(_components.section, {\n id: \"-integrate-with-other-backend-framework\",\n children: [_jsxs(_components.h2, {\n id: \"-integrate-with-other-backend-framework\",\n children: [_jsx(_components.span, {\n role: \"img\",\n \"aria-label\": \"shuffle tracks button\",\n children: \"๐Ÿ”€\"\n }), \" Integrate with other backend framework\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"When you use \", _jsx(_components.code, {\n children: \"runApp(App())\"\n }), \" on the server-side, jaspr spins up its own http server to serve all\\nincoming requests. However depending on your app, you may want to add your own request handling and\\nserver setup, e.g. if you want to add an api to your application.\"]\n }), \"\\n\", _jsx(_components.p, {\n children: \"Jaspr provides two ways to control the server setup:\"\n }), \"\\n\"]\n }), _jsxs(_components.section, {\n id: \"1-extend-the-default-request-handling-by-adding-middleware-to-serverapp\",\n children: [_jsxs(_components.h4, {\n id: \"1-extend-the-default-request-handling-by-adding-middleware-to-serverapp\",\n children: [\"1. Extend the default request handling by adding middleware to \", _jsx(_components.code, {\n children: \"ServerApp\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"On the server, the \", _jsx(_components.code, {\n children: \"runApp()\"\n }), \" function will actually return an instance of \", _jsx(_components.code, {\n children: \"ServerApp\"\n }), \" where you can\\nextend the server and request handling of your application.\\nTo make this available, change your jaspr import to \", _jsx(_components.code, {\n children: \"import 'package:jaspr/jaspr_server.dart';\"\n }), \". Then\\nchain some calls after the \", _jsx(_components.code, {\n children: \"runApp()\"\n }), \" function, e.g. \", _jsx(_components.code, {\n children: \"runApp(App()).addMiddleware(someShelfMiddleware)\"\n }), \".\\nJaspr uses \", _jsx(_components.code, {\n children: \"package:shelf\"\n }), \" for this to be compatible with most common dart backends. You can also:\"]\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [\"add a listener using \", _jsx(_components.code, {\n children: \".setListener((server) { ... })\"\n }), \".\\nThis will run the callback when the http server first started listening for requests, and then\\neach time the app is reloaded through auto-reload.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [\"add a builder using \", _jsx(_components.code, {\n children: \".setBuilder((handler) => myHttpServerImpl(handler))\"\n }), \".\\nA builder has to return a \", _jsx(_components.code, {\n children: \"HttpServer\"\n }), \", which will completely replace the default server created\\nby jaspr.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [\"check out \", _jsx(_components.a, {\n href: \"https://github.com/schultek/jaspr/tree/main/examples/custom_server_middleware\",\n children: \"the server_handling example\"\n }), \" to see this in action.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), _jsxs(_components.section, {\n id: \"2-add-jaspr-to-any-existing-backend-implementation--framework-as-a-shelf-handler\",\n children: [_jsxs(_components.h4, {\n id: \"2-add-jaspr-to-any-existing-backend-implementation--framework-as-a-shelf-handler\",\n children: [\"2. Add jaspr to any existing backend implementation / framework as a \", _jsx(_components.strong, {\n children: \"shelf handler\"\n }), \".\"]\n }), \"\\n\", _jsx(_components.p, {\n children: \"Instead of letting jaspr run your app and manage your http server, you can also use your favourite\\nbackend package or framework, and simply add jaspr as a request handler to your implementation.\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"To do this, remove the \", _jsx(_components.code, {\n children: \"runApp()\"\n }), \" call and setup your backend as you normally would. When defining your\\nroute handlers, create a handler for jaspr by using \", _jsx(_components.code, {\n children: \"serveApp\"\n }), \" like this:\"]\n }), \"\\n\", _jsx(_components.pre, {\n raw: \"var handler = serveApp((request, render) {\\n// Optionally do something with `request`\\nprint(\\\"Request uri is ${request.requestedUri}\\\");\\n// Return a server-rendered response by calling `render()` with your root component\\nreturn render(App());\\n});\\n\\n// provide `handler` to your app, e.g.\\nawait shelf_io.serve(handler, InternetAddress.anyIPv4, 8080);\\n\",\n language: \"dart\",\n html: \"
var handler = serveApp((request, render) {\\n// Optionally do something with `request`\\nprint(\\\"Request uri is ${request.requestedUri}\\\");\\n// Return a server-rendered response by calling `render()` with your root component\\nreturn render(App());\\n});\\n\\n// provide `handler` to your app, e.g.\\nawait shelf_io.serve(handler, InternetAddress.anyIPv4, 8080);\\n
\",\n children: _jsx(_components.code, {\n className: \"language-dart\",\n children: \"var handler = serveApp((request, render) {\\n// Optionally do something with `request`\\nprint(\\\"Request uri is ${request.requestedUri}\\\");\\n// Return a server-rendered response by calling `render()` with your root component\\nreturn render(App());\\n});\\n\\n// provide `handler` to your app, e.g.\\nawait shelf_io.serve(handler, InternetAddress.anyIPv4, 8080);\\n\"\n })\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Check out \", _jsx(_components.a, {\n href: \"https://github.com/schultek/jaspr/tree/main/examples/shelf_backend\",\n children: \"the shelf_handler example\"\n }), \" to see this in action.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Be aware\"\n }), \" that using the \", _jsx(_components.code, {\n children: \"serveApp\"\n }), \" handler does not work with auto-reload. When building a simple\\nshelf backend, it is better to use \", _jsx(_components.code, {\n children: \"runApp()\"\n }), \" with a custom \", _jsx(_components.code, {\n children: \".setBuilder()\"\n }), \" to keep this functionality.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Make sure\"\n }), \" that you set the base path of your app when mounting the \", _jsx(_components.code, {\n children: \"serveApp\"\n }), \" handler to\\nany other route prefix than \", _jsx(_components.code, {\n children: \"/\"\n }), \", otherwise your static resources like \", _jsx(_components.code, {\n children: \"styles.css\"\n }), \" or \", _jsx(_components.code, {\n children: \"main.dart.js\"\n }), \"\\ncan't be loaded correctly. Add the \", _jsx(_components.code, {\n children: \"/\\\">\"\n }), \" tag to the \", _jsx(_components.code, {\n children: \"head\"\n }), \" of your\\n\", _jsx(_components.code, {\n children: \"index.html\"\n }), \", as demonstrated in the example.\"]\n })]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = props.components || ({});\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\nfunction _missingMdxReference(id, component) {\n throw new Error(\"Expected \" + (component ? \"component\" : \"object\") + \" `\" + id + \"` to be defined: you likely forgot to import, pass, or provide it.\");\n}\n"},"preview":false}

๐Ÿ— Server Side Rendering

Jaspr is built to be server-side-rendering first. It leverages the power of Darts cross-compilation to both js and native executables to execute the same app on the server and client.

For each target (server and client) you need to have a separate entrypoint to start your app. However depending on what project setup you choose, the web entrypoint might be automatically generated for you.

  1. The entrypoint on the web will always be the corresponding file inside the web directory, usually web/main.dart. This file will be compiled to js and is included in the index.html file as a script: <script defer src="main.dart.js"></script>

  2. The entrypoint on the server will:

    • be the file specified using the -i (--input) option when running jaspr serve or jaspr build or
    • default to lib/main.dart

๐Ÿ’ฝ Loading Data on the Server

When using server side rendering, you want the ability to load some data data before rendering the html. With jaspr this is build into the package and easy to do.

Start by using the PreloadStateMixin on a StatefulComponents State class and implement the Future<T> preloadState() method.

class MyState extends State<MyStatefulComponent> with PreloadStateMixin {
  
  @override
  Future<void> preloadState() async {
    ...
  }
}

This method will only be executed on the server and will be called before initState(). It will defer initState() as well as building the component until the future returned by the method is completed.

While it is only executed on the server it still will be compiled for the client, since Dart does not have selective compilation. So you have to make sure it will compile on the client, e.g. by using service locators and providing mock services on the client. (TODO: More explanation and How To)

This is a great use-case for jasprs platform-specific imports

โ™ป๏ธ Syncing Data to the Client

Alongside with preloading some data on the server, you often want to also sync the data with the client. You can simply add the SyncStateMixin which will automatically sync state from the server with the client.

The SyncStateMixin accepts a second type argument for the data type that you want to sync. You the have to implement the the getState and updateState() methods.

class MyState extends State<MyStatefulComponent> with SyncStateMixin<MyStatefulComponent, MyStateModel> {

  MyStateModel? model;
  
  // a globally unique id that is used to identify the state
  @override
  String get syncId => 'my_id';

  // this will get the state to be sent to the client
  // and is only executed on the server
  @override
  MyStateModel? getState() {
    return model;
  }

  // this will receive the state on the client
  // and it is safe to call setState
  void updateState(MyStateModel? value) {
    setState(() {
      model = value;
    });
  }
}

In order to send the data, it needs to be serialized on the server and deserialized on the client. The serialization format is defined by the syncCodec getter defined in SyncStateMixin.

The codec must encode to a primitive or object value of one of the supported types: Null, bool, double, int, Uint8List, String, Map, List. By default, the codec is null and your state must already be one of the supported types.

When you want to use another type like a custom model class, you have to override the syncCodec getter, which has to return a Codec that encodes to String. This can be any codec, however a typical use would be to encode your model to Map<String, dynamic> and the fuse this with the json codec to get a json string.


class MyState extends State<MyStatefulComponent> with SyncStateMixin<MyStatefulComponent, MyStateModel> {

  @override
  Codec<MyStateModel, String> get syncCodec => MyStateModelCodec();

  ...
}

// codec that encodes a value of MyStateModel to Map<String, dynamic>
class MyStateModelCodec extends Codec<MyStateModel, Map<String, dynamic>> {
  
  ...
  
}

๐Ÿ”€ Integrate with other backend framework

When you use runApp(App()) on the server-side, jaspr spins up its own http server to serve all incoming requests. However depending on your app, you may want to add your own request handling and server setup, e.g. if you want to add an api to your application.

Jaspr provides two ways to control the server setup:

1. Extend the default request handling by adding middleware to ServerApp.

On the server, the runApp() function will actually return an instance of ServerApp where you can extend the server and request handling of your application. To make this available, change your jaspr import to import 'package:jaspr/jaspr_server.dart';. Then chain some calls after the runApp() function, e.g. runApp(App()).addMiddleware(someShelfMiddleware). Jaspr uses package:shelf for this to be compatible with most common dart backends. You can also:

  • add a listener using .setListener((server) { ... }). This will run the callback when the http server first started listening for requests, and then each time the app is reloaded through auto-reload.

  • add a builder using .setBuilder((handler) => myHttpServerImpl(handler)). A builder has to return a HttpServer, which will completely replace the default server created by jaspr.

  • check out the server_handling example to see this in action.

2. Add jaspr to any existing backend implementation / framework as a shelf handler.

Instead of letting jaspr run your app and manage your http server, you can also use your favourite backend package or framework, and simply add jaspr as a request handler to your implementation.

To do this, remove the runApp() call and setup your backend as you normally would. When defining your route handlers, create a handler for jaspr by using serveApp like this:

var handler = serveApp((request, render) {
// Optionally do something with `request`
print("Request uri is ${request.requestedUri}");
// Return a server-rendered response by calling `render()` with your root component
return render(App());
});

// provide `handler` to your app, e.g.
await shelf_io.serve(handler, InternetAddress.anyIPv4, 8080);

Check out the shelf_handler example to see this in action.

Be aware that using the serveApp handler does not work with auto-reload. When building a simple shelf backend, it is better to use runApp() with a custom .setBuilder() to keep this functionality.

Make sure that you set the base path of your app when mounting the serveApp handler to any other route prefix than /, otherwise your static resources like styles.css or main.dart.js can't be loaded correctly. Add the <base href="/<route_prefix>/"> tag to the head of your index.html, as demonstrated in the example.