> ## Documentation Index
> Fetch the complete documentation index at: https://www.latitude.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Egress

> Traffic to and from your Latitude.sh storage is free

export const ReactFlowDiagram = ({nodes = [], edges = [], height = 'md', className, interactive = false, showControls = interactive, showMiniMap = false, showBackground = true, fitView = true, fitViewPadding = 0.25, orientation = 'horizontal'}) => {
  const reactFlowVersion = '12.10.2';
  const reactVersion = '18.3.1';
  const reactUrl = `https://unpkg.com/react@${reactVersion}/umd/react.production.min.js`;
  const reactDomUrl = `https://unpkg.com/react-dom@${reactVersion}/umd/react-dom.production.min.js`;
  const reactFlowUrl = `https://unpkg.com/@xyflow/react@${reactFlowVersion}/dist/umd/index.js`;
  const reactFlowCssUrl = `https://unpkg.com/@xyflow/react@${reactFlowVersion}/dist/style.css`;
  const heightClassNames = {
    sm: 'h-64',
    md: 'h-96'
  };
  const heightStyles = {
    lg: {
      height: '560px'
    },
    xl: {
      height: '820px'
    }
  };
  const nodeToneClassNames = {
    default: 'border-zinc-950/20 bg-white text-zinc-950 dark:border-white/20 dark:bg-zinc-950 dark:text-white',
    accent: 'border-violet-500/30 bg-violet-50 text-zinc-950 dark:border-violet-400/30 dark:bg-violet-950/40 dark:text-white',
    success: 'border-emerald-500/30 bg-emerald-50 text-zinc-950 dark:border-emerald-400/30 dark:bg-emerald-950/40 dark:text-white',
    warning: 'border-amber-500/30 bg-amber-50 text-zinc-950 dark:border-amber-400/30 dark:bg-amber-950/40 dark:text-white'
  };
  const classNames = (...values) => values.filter(Boolean).join(' ');
  const containerRef = useRef(null);
  const rootRef = useRef(null);
  const [errorMessage, setErrorMessage] = useState(null);
  const heightStyle = heightStyles[height];
  const heightClassName = heightClassNames[height] || (heightStyle ? null : height);
  const isVertical = orientation === 'vertical';
  const loadScript = src => {
    if (document.querySelector(`script[src="${src}"]`)) return Promise.resolve();
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.async = true;
      script.onload = resolve;
      script.onerror = () => reject(new Error(`Failed to load ${src}`));
      document.head.appendChild(script);
    });
  };
  const loadStylesheet = href => {
    if (document.querySelector(`link[href="${href}"]`)) return;
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    document.head.appendChild(link);
  };
  const ensureJsxRuntime = () => {
    if (window.jsxRuntime || !window.React) return;
    const createElement = (type, props = {}, key) => {
      const elementProps = key === undefined ? props : {
        ...props,
        key
      };
      return window.React.createElement(type, elementProps);
    };
    window.jsxRuntime = {
      Fragment: window.React.Fragment,
      jsx: createElement,
      jsxs: createElement
    };
  };
  const loadReactFlow = async () => {
    if (window.ReactFlow) return window.ReactFlow;
    if (!window.__latitudeReactFlowLoader) {
      window.__latitudeReactFlowLoader = Promise.resolve().then(() => loadStylesheet(reactFlowCssUrl)).then(() => window.React ? undefined : loadScript(reactUrl)).then(() => ensureJsxRuntime()).then(() => window.ReactDOM ? undefined : loadScript(reactDomUrl)).then(() => loadScript(reactFlowUrl)).then(() => window.ReactFlow).catch(err => {
        window.__latitudeReactFlowLoader = null;
        return Promise.reject(err);
      });
    }
    return window.__latitudeReactFlowLoader;
  };
  const createDiagramNode = (React, Handle, Position) => {
    const DiagramNode = ({data}) => {
      const positions = {
        top: Position.Top,
        right: Position.Right,
        bottom: Position.Bottom,
        left: Position.Left
      };
      const targetPosition = positions[data?.targetPosition] || (isVertical ? Position.Top : Position.Left);
      const sourcePosition = positions[data?.sourcePosition] || (isVertical ? Position.Bottom : Position.Right);
      if (data?.kind === 'label') {
        const isBadge = data?.tone === 'badge';
        return React.createElement('div', {
          className: isBadge ? 'rounded border px-1.5 py-0.5 text-xs font-semibold leading-none' : 'text-sm font-semibold leading-5 text-zinc-900 dark:text-zinc-100',
          style: {
            backgroundColor: isBadge ? 'var(--latitude-diagram-label-bg)' : undefined,
            borderColor: isBadge ? 'hsl(var(--latitude-diagram-border-strong))' : undefined,
            color: 'hsl(var(--latitude-diagram-node-text))',
            textAlign: data?.align || 'left',
            width: data?.width || 320
          }
        }, data?.title);
      }
      if (data?.kind === 'boundary') {
        return React.createElement('div', {
          className: 'rounded-xl border border-dashed border-zinc-950/70 bg-transparent dark:border-white/70',
          style: {
            width: data?.width || 640,
            height: data?.height || 520
          }
        }, React.createElement(Handle, {
          type: 'target',
          position: targetPosition,
          isConnectable: false,
          className: 'opacity-0'
        }), React.createElement('div', {
          className: 'px-4 py-3 text-sm font-semibold leading-5 text-zinc-800 dark:text-zinc-100'
        }, data?.title), React.createElement(Handle, {
          type: 'source',
          position: sourcePosition,
          isConnectable: false,
          className: 'opacity-0'
        }));
      }
      if (data?.kind === 'zone') {
        return React.createElement('div', {
          className: 'rounded-xl border border-violet-300/70 bg-violet-100/50 dark:border-violet-400/30 dark:bg-violet-950/30',
          style: {
            width: data?.width || 520,
            height: data?.height || 300
          }
        }, React.createElement(Handle, {
          type: 'target',
          position: targetPosition,
          isConnectable: false,
          className: 'opacity-0'
        }), React.createElement('div', {
          className: 'px-4 py-3 text-sm font-medium leading-5 text-zinc-700 dark:text-zinc-200'
        }, data?.title), React.createElement(Handle, {
          type: 'source',
          position: sourcePosition,
          isConnectable: false,
          className: 'opacity-0'
        }));
      }
      if (data?.kind === 'container') {
        const containerHandles = [{
          type: 'target',
          position: Position.Top,
          id: 'target-top'
        }, {
          type: 'source',
          position: Position.Top,
          id: 'source-top'
        }, {
          type: 'target',
          position: Position.Right,
          id: 'target-right'
        }, {
          type: 'source',
          position: Position.Right,
          id: 'source-right'
        }, {
          type: 'target',
          position: Position.Bottom,
          id: 'target-bottom'
        }, {
          type: 'source',
          position: Position.Bottom,
          id: 'source-bottom'
        }, {
          type: 'target',
          position: Position.Left,
          id: 'target-left'
        }, {
          type: 'source',
          position: Position.Left,
          id: 'source-left'
        }];
        return React.createElement('div', {
          style: {
            width: data?.width || 480,
            height: data?.height || 200,
            border: data?.border || '1px solid hsl(var(--latitude-diagram-brand-uv) / 0.35)',
            backgroundColor: data?.backgroundColor || 'transparent',
            borderRadius: data?.borderRadius ?? 12,
            boxSizing: 'border-box'
          }
        }, ...containerHandles.map(handle => React.createElement(Handle, {
          key: `${handle.type}-${handle.id}`,
          type: handle.type,
          position: handle.position,
          id: handle.id,
          isConnectable: false,
          className: 'opacity-0'
        })));
      }
      if (data?.kind === 'resource') {
        const icon = data?.icon || data?.badge || '';
        const hexColor = data?.hexColor || '#2563eb';
        return React.createElement('div', {
          className: 'flex w-32 flex-col items-center text-center text-zinc-800 dark:text-zinc-100'
        }, React.createElement(Handle, {
          type: 'target',
          position: targetPosition,
          isConnectable: false,
          className: 'opacity-0'
        }), React.createElement('div', {
          className: 'flex h-20 w-20 items-center justify-center text-lg font-bold tracking-wide text-white shadow-sm',
          style: {
            backgroundColor: hexColor,
            clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)'
          }
        }, icon), React.createElement('div', {
          className: 'mt-2 text-sm font-semibold leading-5'
        }, data?.title), data?.subtitle ? React.createElement('div', {
          className: 'mt-0.5 text-xs leading-4 text-zinc-600 dark:text-zinc-300'
        }, data.subtitle) : null, React.createElement(Handle, {
          type: 'source',
          position: sourcePosition,
          isConnectable: false,
          className: 'opacity-0'
        }));
      }
      const tone = nodeToneClassNames[data?.tone] || nodeToneClassNames.default;
      const width = data?.width || (data?.size === 'wide' ? 320 : 256);
      const blockColumns = data?.blockColumns || 3;
      return React.createElement('div', {
        className: classNames('rounded-lg border px-4 py-3 shadow-sm', tone),
        style: {
          width
        }
      }, React.createElement(Handle, {
        type: 'target',
        position: targetPosition,
        isConnectable: false,
        className: 'opacity-0'
      }), React.createElement('div', {
        className: 'flex items-start justify-between gap-3'
      }, React.createElement('div', {
        className: 'min-w-0'
      }, React.createElement('div', {
        className: 'truncate text-base font-semibold leading-6'
      }, data?.title), data?.subtitle ? React.createElement('div', {
        className: 'mt-1 text-sm leading-5 text-zinc-600 dark:text-zinc-300'
      }, data.subtitle) : null), data?.badge ? React.createElement('span', {
        className: 'shrink-0 rounded-full border border-zinc-950/10 px-1.5 py-0.5 text-xs font-medium leading-none text-zinc-600 dark:border-white/20 dark:text-zinc-300'
      }, data.badge) : null), data?.blocks?.length ? React.createElement('div', {
        className: 'mt-3',
        style: {
          display: 'grid',
          gap: '0.5rem',
          gridTemplateColumns: `repeat(${blockColumns}, minmax(0, 1fr))`
        }
      }, ...data.blocks.map((block, index) => {
        const blockData = typeof block === 'string' ? {
          label: block
        } : block;
        return React.createElement('div', {
          key: `${blockData.label}-${index}`,
          className: 'flex min-h-14 items-center justify-center rounded-md border border-zinc-950/10 bg-zinc-950/[0.03] px-2 text-center text-xs font-medium leading-4 text-zinc-700 dark:border-white/15 dark:bg-white/10 dark:text-zinc-200'
        }, blockData.label);
      })) : null, React.createElement(Handle, {
        type: 'source',
        position: sourcePosition,
        isConnectable: false,
        className: 'opacity-0'
      }));
    };
    return React.memo(DiagramNode);
  };
  const normalizeNodes = (inputNodes, Position) => inputNodes.map(node => {
    const positions = {
      top: Position.Top,
      right: Position.Right,
      bottom: Position.Bottom,
      left: Position.Left
    };
    return {
      draggable: false,
      selectable: false,
      ...node,
      type: node.type || 'diagramNode',
      sourcePosition: positions[node.sourcePosition] || positions[node.data?.sourcePosition] || (isVertical ? Position.Bottom : Position.Right),
      targetPosition: positions[node.targetPosition] || positions[node.data?.targetPosition] || (isVertical ? Position.Top : Position.Left),
      data: {
        title: node.label || node.id,
        ...node.data || ({})
      }
    };
  });
  const normalizeEdges = inputEdges => inputEdges.map(edge => ({
    type: 'smoothstep',
    animated: true,
    ...edge,
    style: {
      stroke: 'hsl(var(--latitude-diagram-border-strong))',
      strokeWidth: 1.5,
      strokeDasharray: '5 7',
      ...edge.style || ({})
    },
    ...edge.markerEnd ? {
      markerEnd: edge.markerEnd
    } : {}
  }));
  const renderDiagram = ({container}) => {
    const React = window.React;
    const ReactDOM = window.ReactDOM;
    const {Background, Controls, Handle, MiniMap, Position, ReactFlow} = window.ReactFlow;
    const nodeTypes = {
      diagramNode: createDiagramNode(React, Handle, Position)
    };
    const children = [showBackground ? React.createElement(Background, {
      key: 'background'
    }) : null, showMiniMap ? React.createElement(MiniMap, {
      key: 'minimap',
      pannable: interactive,
      zoomable: interactive
    }) : null, showControls ? React.createElement(Controls, {
      key: 'controls',
      showInteractive: false
    }) : null].filter(Boolean);
    const flowElement = React.createElement(ReactFlow, {
      nodes: normalizeNodes(nodes, Position),
      edges: normalizeEdges(edges),
      nodeTypes,
      fitView,
      fitViewOptions: {
        padding: fitViewPadding
      },
      nodesDraggable: interactive,
      nodesConnectable: false,
      elementsSelectable: interactive,
      panOnDrag: interactive,
      panOnScroll: false,
      zoomOnScroll: false,
      zoomOnPinch: interactive,
      zoomOnDoubleClick: interactive,
      preventScrolling: false
    }, ...children);
    const root = ReactDOM.createRoot ? ReactDOM.createRoot(container) : {
      render: element => ReactDOM.render(element, container),
      unmount: () => ReactDOM.unmountComponentAtNode(container)
    };
    root.render(flowElement);
    return root;
  };
  useEffect(() => {
    let isMounted = true;
    loadReactFlow().then(() => {
      if (!isMounted || !containerRef.current) return;
      rootRef.current?.unmount();
      rootRef.current = renderDiagram({
        container: containerRef.current
      });
    }).catch(error => {
      if (!isMounted) return;
      setErrorMessage(error.message);
    });
    return () => {
      isMounted = false;
      rootRef.current?.unmount();
      rootRef.current = null;
    };
  }, [nodes, edges, interactive, showControls, showMiniMap, showBackground, fitView, fitViewPadding, orientation]);
  return <div className={classNames('latitude-react-flow-diagram not-prose my-6 overflow-hidden rounded-xl border border-zinc-950/10 bg-zinc-50 dark:border-white/10 dark:bg-zinc-950/50', heightClassName, className)} data-interactive={interactive ? 'true' : 'false'} style={heightStyle}>
      <style>
        {`
          .latitude-react-flow-diagram {
            --latitude-diagram-brand-uv: var(--brand-uv, 241 87% 61%);
            --latitude-diagram-border: var(--hsl-border, 0 0% 92%);
            --latitude-diagram-border-strong: 210 4% 53%;
            --latitude-diagram-node-bg: 0 0% 100%;
            --latitude-diagram-node-text: 210 4% 20%;
            --latitude-diagram-node-border: 210 4% 28%;
            --latitude-diagram-label-bg: #ffffff;
            --latitude-diagram-surface: 0 0% 100%;
          }

          .dark .latitude-react-flow-diagram {
            --latitude-diagram-brand-uv: var(--brand-uv, 71 99% 67%);
            --latitude-diagram-border: var(--hsl-border, 71 2% 18%);
            --latitude-diagram-border-strong: 71 1.5% 50%;
            --latitude-diagram-node-bg: 71 1.5% 10%;
            --latitude-diagram-node-text: 71 1.5% 90%;
            --latitude-diagram-node-border: 71 1.5% 38%;
            --latitude-diagram-label-bg: hsl(71 1.5% 7%);
            --latitude-diagram-surface: 71 1.5% 7%;
          }

          .latitude-react-flow-diagram[data-interactive="false"] .react-flow__handle {
            opacity: 0;
          }

          .latitude-react-flow-diagram .react-flow__node-default {
            background: hsl(var(--latitude-diagram-node-bg));
            border: 1px solid hsl(var(--latitude-diagram-node-border));
            border-radius: 6px;
            box-shadow: 0 1px 2px hsl(0 0% 0% / 0.06);
            color: hsl(var(--latitude-diagram-node-text));
            font-size: 13px;
            line-height: 1.2;
            padding: 10px 14px;
            white-space: nowrap;
          }

          .latitude-react-flow-diagram .react-flow__edge.animated path {
            animation-duration: 1.7s;
          }

          .dark .latitude-react-flow-diagram .react-flow__node-default {
            background: hsl(var(--latitude-diagram-node-bg));
            border-color: hsl(var(--latitude-diagram-brand-uv) / 0.35);
            box-shadow: 0 0 0 1px hsl(0 0% 0% / 0.2);
            color: hsl(var(--latitude-diagram-node-text));
          }
        `}
      </style>
      {errorMessage ? <div className="flex h-full items-center justify-center px-4 text-sm text-red-600 dark:text-red-400">
          React Flow failed to load: {errorMessage}
        </div> : <div ref={containerRef} className="h-full w-full" />}
    </div>;
};

export const objectStorageNodes = [{
  id: 'title',
  position: {
    x: 0,
    y: 0
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title: 'Object storage — S3 over the public internet',
    width: 720,
    align: 'center'
  }
}, {
  id: 'internet',
  position: {
    x: 80,
    y: 60
  },
  type: 'default',
  data: {
    label: 'Internet / external clients'
  },
  style: {
    width: 220
  },
  sourcePosition: 'bottom'
}, {
  id: 'server',
  position: {
    x: 420,
    y: 60
  },
  type: 'default',
  data: {
    label: 'Your Latitude server'
  },
  style: {
    width: 220
  },
  sourcePosition: 'bottom'
}, {
  id: 'object',
  position: {
    x: 250,
    y: 320
  },
  type: 'default',
  data: {
    label: 'Object storage (S3 endpoint)'
  },
  style: {
    width: 220
  },
  targetPosition: 'top'
}, {
  id: 'label-internet-object',
  position: {
    x: 20,
    y: 195
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    tone: 'badge',
    title: 'Free (both directions)',
    width: 180,
    align: 'center'
  }
}, {
  id: 'label-server-object',
  position: {
    x: 520,
    y: 195
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    tone: 'badge',
    title: 'Free (both directions)',
    width: 180,
    align: 'center'
  }
}];

export const arrowMarker = {
  type: 'arrowclosed',
  width: 16,
  height: 16
};

export const objectStorageEdges = [{
  id: 'internet-object',
  source: 'internet',
  target: 'object',
  markerEnd: arrowMarker
}, {
  id: 'server-object',
  source: 'server',
  target: 'object',
  markerEnd: arrowMarker
}];

export const nodeContainerStyle = {
  border: '1px solid hsl(var(--latitude-diagram-node-border))',
  backgroundColor: 'hsl(var(--latitude-diagram-node-bg))',
  borderRadius: 6
};

export const buildLocalStorageNodes = ({title, storageLabel}) => [{
  id: 'title',
  position: {
    x: 0,
    y: 0
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title,
    width: 720,
    align: 'center'
  }
}, {
  id: 'server',
  position: {
    x: 240,
    y: 60
  },
  type: 'diagramNode',
  data: {
    kind: 'container',
    width: 240,
    height: 60,
    ...nodeContainerStyle
  }
}, {
  id: 'server-label',
  position: {
    x: 240,
    y: 80
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title: 'Your Latitude server',
    width: 240,
    align: 'center'
  }
}, {
  id: 'storage',
  position: {
    x: 240,
    y: 280
  },
  type: 'diagramNode',
  data: {
    kind: 'container',
    width: 240,
    height: 60,
    ...nodeContainerStyle
  }
}, {
  id: 'storage-label',
  position: {
    x: 240,
    y: 300
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title: storageLabel,
    width: 240,
    align: 'center'
  }
}, {
  id: 'label-write',
  position: {
    x: 520,
    y: 165
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    tone: 'badge',
    title: `Server → ${storageLabel.toLowerCase()}: free`,
    width: 200,
    align: 'center'
  }
}, {
  id: 'label-read',
  position: {
    x: 20,
    y: 165
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    tone: 'badge',
    title: `${storageLabel} → server: free`,
    width: 200,
    align: 'center'
  }
}];

export const localStorageEdges = [{
  id: 'server-to-storage',
  source: 'server',
  sourceHandle: 'source-right',
  target: 'storage',
  targetHandle: 'target-right',
  markerEnd: arrowMarker
}, {
  id: 'storage-to-server',
  source: 'storage',
  sourceHandle: 'source-left',
  target: 'server',
  targetHandle: 'target-left',
  markerEnd: arrowMarker
}];

export const blockStorageNodes = buildLocalStorageNodes({
  title: 'Block storage — storage network',
  storageLabel: 'Block volume'
});

export const blockStorageEdges = localStorageEdges;

export const fileStorageNodes = buildLocalStorageNodes({
  title: 'File storage — storage network',
  storageLabel: 'File system'
});

export const fileStorageEdges = localStorageEdges;

Latitude.sh storage products do not charge egress fees. Traffic to and from your storage is free, in either direction, regardless of the storage product. The network path differs by product, because Object storage and Block / File storage are reached differently:

* **Object storage** is reached over the public internet through its S3 endpoint.
* **Block and File storage** attach over the storage network.

## Summary

| Direction                    | Billed? |
| :--------------------------- | :------ |
| Object storage ↔ internet    | Free    |
| Object storage ↔ your server | Free    |
| Block storage ↔ your server  | Free    |
| File storage ↔ your server   | Free    |

## Object storage

Object storage is reached over the public internet through its [S3 endpoint](/storage/object-storage#connection-details). Traffic between your Latitude.sh server and the bucket leaves the server through its public network interface, just like any other internet request.

<ReactFlowDiagram nodes={objectStorageNodes} edges={objectStorageEdges} height="lg" fitViewPadding={0.04} />

* **Traffic out of a bucket is free**, no matter where it goes. Reads to your Latitude.sh server, to an application on your laptop, or to another cloud are not billed by Latitude.sh.
* **Traffic into a bucket is free**, whether it comes from the internet or from your Latitude.sh server.

If you connect from outside Latitude.sh (for example, from a laptop or another provider), Latitude.sh does not charge for the traffic on its side. Your local ISP or cloud provider may have its own egress fees.

## Block storage

Block volumes attach to bare metal servers over Latitude.sh's storage network using a block protocol. They never touch the public internet.

<ReactFlowDiagram nodes={blockStorageNodes} edges={blockStorageEdges} height="lg" fitViewPadding={0.04} />

* **All traffic between your server and the volume is free**, in either direction.

## File storage

File systems attach to bare metal servers over Latitude.sh's storage network using a file protocol. They never touch the public internet.

<ReactFlowDiagram nodes={fileStorageNodes} edges={fileStorageEdges} height="lg" fitViewPadding={0.04} />

* **All traffic between your server and the file system is free**, in either direction.

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Is uploading to a bucket billed as a storage fee?">
    No. Latitude.sh Object storage does not charge for egress. Uploads to a
    bucket are free.
  </Accordion>

  <Accordion title="Does this change if I'm uploading from outside Latitude.sh?">
    No. Uploads to a bucket from a laptop, another cloud, or any
    non-Latitude.sh source are free on the Latitude.sh side. Your origin's own
    provider may have its own egress fees.
  </Accordion>
</AccordionGroup>
