TIDE • Tips Agent

Production Patterns & SolidJS Micro-Islands

Real-world architecture patterns for Astro 5 static shells with dynamic SolidJS micro-islands.

Production Patterns & Examples

Production architecture patterns for embedding reactive SolidJS micro-islands inside Astro 5 static templates.

Astro 5 Island Integration

Mount reactive Tide micro-islands inside static Astro pages using client directives:

astro
---
// src/pages/dashboard.astro
import BaseLayout from "../layouts/BaseLayout.astro";
import { DashboardMetricsIsland } from "../components/DashboardIsland";
---
<BaseLayout title="System Dashboard">
  <div class="max-w-7xl mx-auto px-4 py-8">
    <h1 class="text-2xl font-bold">Local Agent Dashboard</h1>
    <!-- Client directive delays hydration until idle -->
    <DashboardMetricsIsland client:idle />
  </div>
</BaseLayout>

URL Shorthand with Dynamic ETag Verification

Enable etag: true to send standard If-None-Match HTTP headers. When the server responds with 304 Not Modified, Tide avoids parsing JSON and skips re-rendering.

tsx
import { createTide } from '@uikode/tide';

export function AgentStatusTile() {
  const agent = createTide<{ status: string; uptime: number }>({
    key: 'agent-status',
    url: '/api/agent/status',
    etag: true,
    pollInterval: 15_000,
  });

  return (
    <div class="metric-tile">
      <span>Status: {agent.data()?.status}</span>
    </div>
  );
}

Content Deduplication via hashCompare

hashCompare: true calculates a lightweight 32-bit djb2 hash over the response payload. If the received data matches the current state, Tide skips updating the SolidJS signal, preventing downstream virtual DOM reconciliation.

tsx
const stack = createTide({
  key: 'stack-topology',
  url: '/api/stack/status',
  hashCompare: true,
  pollInterval: 5_000,
});

Reactive Key for Filter & Tab Switching

When using dynamic getter functions for key and url, Tide maintains independent cache slots for each parameter combination.

tsx
import { createSignal, For, Show } from 'solid-js';
import { createTide } from '@uikode/tide';

export function AnalyticsViewer() {
  const [range, setRange] = createSignal<'24h' | '7d' | '30d'>('24h');

  const chart = createTide<Array<{ timestamp: number; value: number }>>({
    key: () => `metrics-chart-${range()}`,
    url: () => `/api/metrics?range=${range()}`,
    staleTime: 30_000,
    cacheTime: 300_000,
    persist: true,
  });

  return (
    <div>
      <div class="flex gap-2 mb-4">
        <For each={['24h', '7d', '30d'] as const}>
          {(tab) => (
            <button
              class={range() === tab ? 'bg-primary text-white' : 'bg-secondary'}
              onClick={() => setRange(tab)}
            >
              {tab}
            </button>
          )}
        </For>
      </div>
      <Show when={!chart.loading()} fallback={<p>Loading chart data...</p>}>
        <pre>{JSON.stringify(chart.data(), null, 2)}</pre>
      </Show>
    </div>
  );
}

Lifecycle Control with pause() and resume()

When applying optimistic mutations, call tide.pause() to prevent background polling from overwriting the pending state. Once the mutation resolves, call tide.resume() and tide.refetch().

tsx
async function handleUpdateStatus(newStatus: string) {
  tide.pause(); // Pause background polling
  tide.mutate((prev) => (prev ? { ...prev, status: newStatus } : null));

  try {
    await fetch('/api/status', {
      method: 'POST',
      body: JSON.stringify({ status: newStatus }),
    });
  } finally {
    tide.resume();
    await tide.refetch();
  }
}