# Render on the server

Produce deterministic SVG on the server and let the browser adopt it as a live diagram without rebuilding or re-laying out the scene.

## When to use this

Use server rendering when the first response must already contain the diagram: a page, email, README image, or cached SVG. The server path uses the same engine and SVG renderer as the browser, but it does not require a DOM.

`renderStatic()` is the small API from [`@grafloria/element`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-element-core). It returns a [`StaticRenderResult`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-core#staticrenderresult) containing `html`, `svg`, `css`, and `snapshot`. Use `svg` when the result is an image or file. Use `html`, `css`, and `snapshot` when the browser must adopt the markup as an interactive diagram.

## Render the server response

Pass positioned nodes and edges to [`renderToStaticSVG()`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-core#rendertostaticsvg). Give the render a fixed size and an `instanceId` when a page contains more than one server-rendered diagram. `fitView: true` frames the content using `fitPadding`.

```ts title="server.ts"
import { renderToStaticSVG, type StaticRenderOptions } from '@grafloria/renderer';

const options: StaticRenderOptions = {
  nodes: [
    { id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } },
    { id: 'load', label: 'Load', position: { x: 260, y: 40 }, size: { width: 150, height: 66 } },
    { id: 'model', label: 'Model', position: { x: 150, y: 170 }, size: { width: 150, height: 66 } },
  ],
  edges: [
    { id: 'extract-load', source: 'extract', target: 'load' },
    { id: 'extract-model', source: 'extract', target: 'model' },
  ],
  width: 520,
  height: 300,
  instanceId: 'pipeline-diagram',
  standalone: true,
};

const result = renderToStaticSVG(options);

export const pageDiagram = {
  html: result.html,
  css: result.css,
  snapshot: result.snapshot,
};

export const imageSvg = result.svg;
```

The response contains three labelled boxes and two connecting edges. `result.svg` is a complete standalone SVG because `standalone` adds the SVG namespace. `result.html` is the layer skeleton intended for a diagram container; send `result.css` in a `<style>` element as well.

The output is deterministic for the same options. Omitted node and edge IDs receive deterministic `node-<i>` and `edge-<i>` IDs, auto-ports receive stable names, and the snapshot carries the instance scope, canvas size, camera origin, and zoom.

For the element package, the equivalent call is `renderStatic(options)`. It is a re-export of the same server implementation:

```ts
import { renderStatic, type StaticRenderOptions } from '@grafloria/element';

const options: StaticRenderOptions = {
  nodes: [{ id: 'a', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } }],
  edges: [],
  width: 520,
  height: 300,
  standalone: true,
};

const { svg } = renderStatic(options);
```

## Adopt the markup in the browser

On the client, pass the server snapshot to [`createDiagram()`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance#creatediagram). Its [`DiagramInstance`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance) uses the existing server DOM, so the visible SVG remains in place while interaction becomes available.

```ts
import { renderStatic } from '@grafloria/element';
import { createDiagram } from '@grafloria/renderer';

const serverDiagram = renderStatic({
  nodes: [
    { id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } },
    { id: 'load', label: 'Load', position: { x: 260, y: 40 }, size: { width: 150, height: 66 } },
  ],
  edges: [{ id: 'extract-load', source: 'extract', target: 'load' }],
  width: 520,
  height: 300,
});

const container = document.getElementById('pipeline') ?? document.body.appendChild(document.createElement('div'));
container.id = 'pipeline';
container.style.height = '300px';
container.style.width = '520px';
container.innerHTML = `<style>${serverDiagram.css}</style>${serverDiagram.html || serverDiagram.svg}`;

const instance = createDiagram(container, {
  hydrate: serverDiagram.snapshot,
});

instance.fitView();
```

In an application, serialize the server object into the page's data transport rather than using the `declare` above. Do not call `createDiagram()` on the server: it requires a browser DOM. Do not replace the server HTML before hydration; that removes the DOM available for adoption.

## Framework integrations

The server call is framework-independent. The client binding differs only in how it receives the returned `html` and `snapshot`.

:::code-group
```tsx title="React"
import { GrafloriaFlow } from '@grafloria/react';
import { renderStatic } from '@grafloria/element';

export function Pipeline() {
  const ssr = renderStatic({ nodes: [{ id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } }], edges: [], width: 520, height: 300, standalone: true });
  return <div style={{ height: 300 }}><GrafloriaFlow ssr={ssr} /></div>;
}
```
```ts title="Angular"
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import { renderStatic } from '@grafloria/element';
import { CanvasNgCanvasNgComponent } from '@grafloria/canvas-ng';
import { createDiagram } from '@grafloria/renderer';

@Component({
  standalone: true,
  imports: [CanvasNgCanvasNgComponent],
  template: '<lib-canvas-ng-canvas-ng></lib-canvas-ng-canvas-ng><div #host style="height: 300px"></div>',
})
export class PipelineComponent implements AfterViewInit {
  @ViewChild('host', { static: true }) host!: ElementRef<HTMLElement>;

  ngAfterViewInit(): void {
    const result = renderStatic({ nodes: [{ id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } }], edges: [], width: 520, height: 300, standalone: true });
    this.host.nativeElement.innerHTML = `<style>${result.css}</style>${result.html || result.svg}<div>${result.svg}</div>`;
    createDiagram(this.host.nativeElement, { hydrate: result.snapshot });
  }
}
```
```tsx title="Qwik server entry"
import { renderToStaticSVG } from '@grafloria/renderer';
import { GrafloriaFlow } from '@grafloria/qwik';

export function renderPipeline() {
  return renderToStaticSVG({
    nodes: [{ id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } }],
    edges: [],
    width: 520,
    height: 300,
  });
}

export { GrafloriaFlow };
```
```ts title="Vue server entry"
import { renderStatic } from '@grafloria/element';
import { GrafloriaFlow } from '@grafloria/vue';

export function renderPipeline() {
  return renderStatic({
    nodes: [{ id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } }],
    edges: [],
    width: 520,
    height: 300,
  });
}

export { GrafloriaFlow };
```
```js title="JavaScript"
import { renderStatic } from '@grafloria/element';
import { createDiagram } from '@grafloria/renderer';

const container = document.getElementById('pipeline') ?? document.body.appendChild(document.createElement('div'));
container.style.height = '300px';
container.style.width = '520px';
const serverDiagram = renderStatic({
  nodes: [{ id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } }],
  edges: [],
  width: 520,
  height: 300,
});
container.innerHTML = `<style>${serverDiagram.css}</style>${serverDiagram.html || serverDiagram.svg}<div>${serverDiagram.svg}</div>`;
createDiagram(container, { hydrate: serverDiagram.snapshot });
```
:::

Each sample renders the same server result into a sized container. Use a framework binding when it owns the diagram component; use the two-call form when the host owns the container.

Custom or HTML-layer nodes are not rendered on the server because they are framework components. They mount on the client inside the correctly transformed HTML layer. SVG-rendered nodes, ports, edges, labels, arrows, and routing are included in the server result and snapshot.

## Options that matter

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `width` | `number` | `800` | Sets the canvas width in CSS pixels. |
| `height` | `number` | `600` | Sets the canvas height in CSS pixels. |
| `zoom` | `number` | `1` | Sets the initial camera zoom. |
| `viewport` | `{ x: number; y: number }` | `{ x: 0, y: 0 }` | Sets the camera origin in world coordinates. |
| `instanceId` | `string` | `'grafloria-ssr'` | Sets the diagram's CSS scope. Give each diagram on a page a different value. |
| `fitView` | `boolean` | `false` | Frames the content instead of using `viewport` and `zoom`. |
| `fitPadding` | `number` | `40` | Sets the padding used by `fitView`, in CSS pixels. |
| `standalone` | `boolean` | `false` | Adds `xmlns` to the SVG so it stands alone as a file. |

## See it running

Open the [server-side export demo](https://grafloria.com/demos/misc/server-side-export.html). Its preview is a pasted SVG, not a mounted canvas: the fingerprint stays the same across independent renders of the same specification, while a different specification changes it.

See the [React starter](https://grafloria.com/react/) for a live framework binding.

## Related

- [How Grafloria works](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/how-grafloria-works) — the shared model and engine.
- [Instance and lifecycle](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/instance-and-lifecycle) — instance setup and teardown.
- [Export diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/export-diagrams) — browser-side SVG, PNG, JPEG, WebP, and PDF export.
- [Edit Mermaid diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/edit-mermaid-diagrams) — export and import text representations.
