Skip to content

Request Scoped Nanostores

Nanostores is a great and super compact state management library that is compatible with multiple frameworks, even with multiple at the same time. Nanostores has been Astro’s recommended option for sharing state between components and between client islands.

Using Nanostores directly with Astro has some caveats, as explained on Astro’s “Why Nano Stores?” FAQ. They are meant for the client-side. Using them on the server, be it on framework components or on the frontmatter of Astro components, may cause problems with data race between requests. Once the page is rendered, the stores on the client won’t have the data from the stores on the server, which may cause flickering and flashing of content on the screen as the client renders a different content from the server.

This integration bridges this gap and enables the use of Nanostores in Astro across server and client isolated on each request.

Installing the dependency

Terminal window
npx astro add @inox-tools/request-nanostores@beta

Prerequisites

When using the Cloudflare adapter, you’ll need to enable AsyncLocalStorage manually.

Demo

A demo reading cookies to set the request state on the server and use it interactively on the client is available here:

This demo project is based on Astro’s with-nanostores template.

How to use

Wrap your stores using the shared function, giving it a name.

The store name must be unique across your entire project!.

import { atom } from 'nanostores';
import { shared } from '@it-astro:request-nanostores';
export const $cart = atom([]);
export const $cart = shared('cart', atom([]));

It doesn’t have to be an atom! You can use any store that is based on an atom:

import { shared } from '@it-astro:request-nanostores';
import { atom, map, deepMap } from 'nanostores';
export const $atom = shared('atom', atom([]));
export const $map = shared('map', map({}));
export const $deepMap = shared('deepMap', deepMap({}));

Example

If you have a comments section on your page, you can create a store that will be shared between the page and all components.

src/stores/comments.ts
import { atom } from 'nanostores';
import { shared } from '@it-astro:request-nanostores';
export const $comments = shared('comments', atom([]));

A Preact component to show the comments.

src/components/Comments.tsx
import { useStore } from '@nanostores/preact';
import { $comments } from '../stores/comments';
export default function Comments() {
const $comments = useStore($comments);
return <ul>
{ $comments.map(comment => <li>{ comment }</li>) }
</ul>;
}

A Solid component to show the number of comments.

src/components/CommentCount.tsx
import { useStore } from '@nanostores/solid';
import { $comments } from '../stores/comments';
export default function Comments() {
const $comments = useStore($comments);
return <p>
Comments: { $comments().length }
</p>
}

And an Astro page that loads the comments for the page and uses both components. The comments will be available for the components to render on the server and on the client for interactivity.

src/pages/article.astro
---
import Comments from '../components/Comments.tsx';
import CommentCount from '../components/CommentCount.tsx';
import { $comments } from '../stores/comments';
$comments.set(await loadCommentsForArticle());
---
<CommentCount client:load />
<Comments client:load />

License

Request Nanostores is available under the MIT license.