# How to Implement Server-Side Rendering with the #[shard] Attribute in Topcoat

> Learn to implement server-side rendering with Topcoat's #[shard] attribute. Effortlessly update page fragments on reactive signal changes for dynamic HTML without full reloads.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Topcoat's `#[shard]` attribute enables automatic server-side rendering of page fragments by re-executing async functions whenever reactive signal dependencies change, delivering updated HTML without full-page reloads.**

The `tokio-rs/topcoat` framework provides a unique approach to partial server-side rendering through the `#[shard]` macro. This attribute transforms regular async functions into reactive server-side fragments that automatically synchronize with client-side state. When you implement server-side rendering with the `#[shard]` attribute, you create fine-grained updates that execute database queries or API calls on the server while maintaining reactive client-side signals.

## Understanding the #[shard] Architecture

The shard system bridges client-side reactivity with server-side execution through a signal-driven request cycle.

### The Signal-Driven Execution Model

Topcoat's architecture relies on **signals** to trigger server-side work. Inside your components, you create signals that hold client-side state—such as text input values or selection states. When these signals change, the framework automatically serializes the new values, sends them to the server, and re-executes the corresponding `#[shard]` function. The returned HTML fragment replaces the previous DOM content without refreshing the entire page.

### Request Context and Async Boundaries

Every shard function receives a `&Cx` (request context) as its first argument, as implemented in [`crates/topcoat-runtime/grammar/src/shard.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/grammar/src/shard.rs). This context provides access to the router, asset bundles, and other per-request services. Because shards execute on the server, you can perform asynchronous work—such as database lookups or external HTTP requests—before constructing the view response.

## Setting Up a Shard Function

A valid shard function must follow specific signature requirements enforced by the Topcoat compiler. The function must be declared as `async` and return a `Result` type. The first parameter must always be `cx: &Cx`, followed by the arguments you want to track for changes.

```rust
// crates/topcoat-runtime/macro/docs/shard.md defines these constraints
#[shard]
async fn combobox_content(cx: &Cx, input: String) -> Result {
    // Perform async server-side work
    let results = search_fruit(cx, &input).await;
    
    view! {
        <div>
            <b>"results:"</b>
            for item in results {
                <div>(item)</div>
            }
        </div>
    }
}

```

The shard only re-executes when the specific arguments passed from the client component change. Topcoat automatically handles the serialization, network request, and DOM diffing when these tracked arguments update.

## Triggering Server-Side Re-renders from the Client

To connect client-side interactivity with server-side rendering, you invoke the shard function inside a component's `view!` macro, passing signal values as arguments. The framework monitors these dependencies and manages the server communication automatically.

In [`examples/shard/src/main.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/shard/src/main.rs), the `combobox` component demonstrates this pattern:

```rust
#[component]
async fn combobox() -> Result {
    view! {
        // Reactive state declaration
        signal input = String::new();

        <div>
            // Update signal on user input
            <input :value=$(input.get())
                   @input=$(|e: Event| input.set(e.target.value))>

            // Server shard automatically re-renders when input changes
            { combobox_content(input: $(input.get())) }
        </div>
    }
}

```

When the user types into the input field, the signal updates, triggering a server request that executes `combobox_content` with the new input value. The server returns fresh HTML, which Topcoat swaps into the DOM.

## Complete Working Example

The full implementation in [`examples/shard/src/main.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/shard/src/main.rs) demonstrates the integration between pages, components, signals, and shards:

```rust
use topcoat::{
    Result,
    asset::{AssetBundle, RouterBuilderAssetExt},
    context::Cx,
    router::{Router, RouterBuilderDiscoverExt, page},
    runtime::{Event, shard},
    view::{component, view},
};

#[tokio::main]
async fn main() {
    topcoat::start(
        Router::builder()
            .assets(AssetBundle::load().unwrap())
            .discover()
            .build(),
    )
    .await
    .unwrap();
}

#[page("/")]
async fn home() -> Result {
    view! {
        <!DOCTYPE html>
        <html>
            <head>
                { topcoat::dev::script() }
                { topcoat::runtime::script() }
            </head>
            <body>
                { combobox() }
            </body>
        </html>
    }
}

#[component]
async fn combobox() -> Result {
    view! {
        signal input = String::new();
        <div>
            <input :value=$(input.get())
                   @input=$(|e: Event| input.set(e.target.value))>
            { combobox_content(input: $(input.get())) }
        </div>
    }
}

#[shard]
async fn combobox_content(cx: &Cx, input: String) -> Result {
    let results = search_fruit(cx, &input).await;
    view! {
        <div>
            <b>"results:"</b>
            for item in results {
                <div>(item)</div>
            }
        </div>
    }
}

async fn search_fruit(_cx: &Cx, input: &str) -> Vec<&'static str> {
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    let needle = input.to_lowercase();
    const FRUIT: [&str; 35] = [
        "apple", "apricot", "banana", "blackberry", "blueberry", "cherry",
        "coconut", "cranberry", "date", "dragonfruit", "elderberry", "fig",
        "grape", "grapefruit", "guava", "honeydew", "kiwi", "lemon",
        "lime", "lychee", "mango", "nectarine", "orange", "papaya",
        "passionfruit", "peach", "pear", "persimmon", "pineapple",
        "plum", "pomegranate", "raspberry", "strawberry", "tangerine",
        "watermelon",
    ];
    FRUIT.into_iter()
        .filter(|fruit| fruit.contains(&needle))
        .collect()
}

```

## Key Implementation Details

When implementing server-side rendering with the `#[shard]` attribute, remember these critical constraints defined in the Topcoat source:

- **Async requirement**: Shard functions must be declared `async` and return `Result`.
- **Context parameter**: The first argument must be `cx: &Cx` to receive the request context.
- **Tracked arguments**: Only arguments explicitly passed in the view call trigger re-execution; untracked data does not invalidate the cache.
- **Response type**: The function must return a `view! { ... }` block or any type implementing `IntoResponse`.
- **Macro location**: The shard parsing logic resides in [`crates/topcoat-runtime/grammar/src/shard.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/grammar/src/shard.rs), with documentation available in [`crates/topcoat-runtime/macro/docs/shard.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/macro/docs/shard.md).

## Summary

- **Server-side fragments**: The `#[shard]` attribute marks functions that execute on the server and return HTML fragments when reactive dependencies change.
- **Signal integration**: Client-side signals drive re-execution; pass signal values directly to shard invocations in your `view!` macros.
- **Request context**: Access router and assets through the mandatory `&Cx` first parameter in every shard function.
- **Async capabilities**: Perform database queries, API calls, or other asynchronous work before rendering the response view.
- **Automatic synchronization**: Topcoat handles serialization, network requests, and DOM updates without manual intervention.

## Frequently Asked Questions

### What are the exact signature requirements for a #[shard] function?

A `#[shard]` function must be declared as `async` and return `Result`. The first parameter must be `cx: &Cx` (the request context), followed by any arguments you want to track for changes. According to [`crates/topcoat-runtime/grammar/src/shard.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/grammar/src/shard.rs), the macro validates these constraints at compile time to ensure proper server-side execution context.

### How does Topcoat know when to re-execute a shard?

Topcoat tracks only the arguments explicitly passed to the shard invocation in your `view!` macro. When any signal value passed as an argument changes, the framework serializes the new values, sends a request to the server, and re-executes the shard function. Arguments not included in the view call are not monitored for changes.

### Can I perform database queries inside a shard function?

Yes. Because `#[shard]` functions execute on the server and receive a `&Cx` context parameter, you can perform any asynchronous work including database queries, external API calls, or file system operations. The shard completes this work before returning the `view!` block that renders the final HTML fragment.

### Where is the #[shard] macro defined in the source code?

The core parsing logic for the `#[shard]` attribute resides in [`crates/topcoat-runtime/grammar/src/shard.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/grammar/src/shard.rs). Additional documentation and usage constraints are specified in [`crates/topcoat-runtime/macro/docs/shard.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/macro/docs/shard.md). The runtime integration that handles the client-server communication is part of the broader `topcoat-runtime` crate.