Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8870f71
Merge pull request #159 from rocicorp/0xcadams/readme
0xcadams Jul 22, 2025
4669647
wip: This adds og image generation.
aboodman Jul 22, 2025
4eceebb
maybe this will work?
aboodman Jul 22, 2025
029b082
ok
aboodman Jul 24, 2025
6e37603
Fix "undefined" in og image when page lacks subtitle
aboodman Jul 26, 2025
cfdb28f
Update sync.mdx
coryhouse Jul 28, 2025
9ddbfed
document cmplit
aboodman Jul 29, 2025
70eefad
New og logo and adjusting typography and layout
alexhking Jul 30, 2025
6bcc94b
Adjustments to og image layout. Fixing status page. Adjusting h1 margin
alexhking Jul 30, 2025
4e04a91
Fixing subtitle distance
alexhking Jul 30, 2025
8c6e59f
Fixing the h1 layout when no description present
alexhking Jul 30, 2025
117dfa6
0.22
aboodman Aug 2, 2025
d04d030
spruce
aboodman Aug 4, 2025
feab86b
chore: update sst deployment example
darkgnotic Aug 5, 2025
090edcd
chore: update sst deployment example
darkgnotic Aug 5, 2025
05d2c8a
chore: update raw aws docs back to CHANGE_STREAMER_URI
darkgnotic Aug 5, 2025
16ac88c
chore: update raw aws docs back to CHANGE_STREAMER_URI
darkgnotic Aug 5, 2025
f01a74a
Fix broken links
aboodman Aug 5, 2025
322837b
spruce
aboodman Aug 7, 2025
6e5d244
fix: changed fallback to 404 and not error
0xcadams Aug 7, 2025
00182f1
fix: update packages
0xcadams Aug 7, 2025
0be4bc4
fix: build errors
0xcadams Aug 7, 2025
c7fcc2f
fix: others
0xcadams Aug 7, 2025
44501ba
fix: update release notes to be included in static pages
0xcadams Aug 7, 2025
a66d608
fix: shared code
0xcadams Aug 7, 2025
86bd2ea
fix: build
0xcadams Aug 7, 2025
d1a1291
Merge pull request #168 from rocicorp/0xcadams/fatal-error
0xcadams Aug 7, 2025
72aced3
Adds guide on deploying `zero-cache` to Sevalla.com
miguelrk Aug 8, 2025
22d7e62
docs: update guide for deploying to sevalla.com
miguelrk Aug 8, 2025
5a37bb1
docs: enhance Sevalla deployment guide with additional steps and clarity
Aug 8, 2025
0b573a8
chore: remove `bun.lock` file pushed by accident
Aug 8, 2025
21261cd
Update contents/docs/deployment.mdx
miguelrk Aug 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/no-explicit-any": "warn"
"@typescript-eslint/no-explicit-any": "warn",
"@next/next/no-img-element": "off"
}
}
92 changes: 92 additions & 0 deletions app/api/og/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// app/api/og/route.tsx
import {ImageResponse} from '@vercel/og';
import {NextRequest} from 'next/server';

export const runtime = 'edge'; // Required for @vercel/og

const muotoBold = new URL('../../fonts/muoto-bold.woff', import.meta.url);
const muotoReg = new URL('../../fonts/muoto-regular.woff', import.meta.url);
const zeroDocsLogo = new URL(
'../../../public/images/zero-docs-logo.svg',
import.meta.url,
);

export async function GET(req: NextRequest) {
const {searchParams} = new URL(req.url);
const title = searchParams.get('title') ?? 'General Purpose Sync for the Web';
const subtitle = searchParams.get('subtitle');

return new ImageResponse(
(
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
width: '100%',
alignItems: 'center',
backgroundColor: 'black',
justifyContent: 'center',
color: 'white',
padding: '0 200px',
textAlign: 'center',
}}
>
<div
style={{
fontSize: 86,
letterSpacing: '-2.58px' /* 86 * -3% */,
fontFamily: 'muoto-bold',
}}
>
{title}
</div>
{subtitle && (
<div
style={{
fontSize: 48,
marginTop: 36,
lineHeight: 1.4,
padding: '0 80px',
color: 'white',
fontWeight: 400,
fontFamily: 'muoto-reg',
}}
>
{subtitle}
</div>
)}
<img
src={await loadFileAsBase64URL(zeroDocsLogo, 'image/svg+xml')}
alt="Zero"
style={{width: 424, height: 80, marginTop: 75}}
/>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: 'muoto-bold',
data: await loadFile(muotoBold),
},
{
name: 'muoto-reg',
data: await loadFile(muotoReg),
},
],
},
);
}

async function loadFileAsBase64URL(url: URL, mimetype: string) {
const buffer = await loadFile(url);
const b64 = Buffer.from(buffer).toString('base64');
return `data:${mimetype};base64,${b64}`;
}

async function loadFile(url: URL) {
const res = await fetch(url);
return await res.arrayBuffer();
}
51 changes: 39 additions & 12 deletions app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import Toc from '@/components/toc';
import Pagination from '@/components/Pagination';
import {page_routes} from '@/lib/routes-config';
import {notFound} from 'next/navigation';
import {getDocsForSlug, getDocsTocs, getPreviousNext} from '@/lib/markdown';
import {Typography} from '@/components/typography';
import CopyContent from '@/components/ui/copy-content';
import {getAllPageSlugs} from '@/lib/get-slugs';

type PageProps = {params: {slug: string[]}};
type PageProps = {params: Promise<{slug: string[]}>};

export default async function DocsPage({params: {slug = []}}: PageProps) {
const pathName = slug.join('/');
export default async function DocsPage({params}: PageProps) {
const {slug: slugParams} = await params;

const pathName = slugParams.join('/');
const [res, tocs, previousNext] = await Promise.all([
getDocsForSlug(pathName),
getDocsTocs(pathName),
Expand All @@ -22,10 +24,12 @@ export default async function DocsPage({params: {slug = []}}: PageProps) {
<div className="flex flex-[4] min-w-0 items-start gap-14">
<div className="flex-[3] min-w-0 py-10">
<Typography>
<h1 className="text-3xl -mt-2">{res.frontmatter.title}</h1>
<p className="-mt-4 text-muted-foreground text-[16.5px]">
{res.frontmatter.description}
</p>
<h1 className="text-3xl -mt-2 mb-12">{res.frontmatter.title}</h1>
{res.frontmatter.description && (
<p className="-mt-10 text-muted-foreground text-[16.5px]">
{res.frontmatter.description}
</p>
)}
{/* Wrap content with CopyableContent */}
<CopyContent content={res.content} />
<Pagination previousNext={previousNext} />
Expand All @@ -36,14 +40,37 @@ export default async function DocsPage({params: {slug = []}}: PageProps) {
);
}

export async function generateMetadata({params: {slug = []}}: PageProps) {
const pathName = slug.join('/');
export async function generateMetadata({params}: PageProps) {
const {slug: slugParams} = await params;
const pathName = slugParams.join('/');
const res = await getDocsForSlug(pathName);
if (!res) return null;
const {frontmatter} = res;
return {title: frontmatter.title, description: frontmatter.description};

const encode = (str: string | undefined) => {
if (!str) return '';
return encodeURIComponent(str);
};

const ogImageUrl = `/api/og?title=${encode(frontmatter.title)}&subtitle=${encode(frontmatter.description)}`;

return {
title: frontmatter.title,
description: frontmatter.description,
openGraph: {
title: frontmatter.title,
description: frontmatter.description,
images: ogImageUrl,
},
twitter: {
card: 'summary_large_image',
title: frontmatter.title,
description: frontmatter.description,
images: ogImageUrl,
},
};
}

export function generateStaticParams() {
return page_routes.map(item => ({slug: item.href.split('/').slice(1)}));
return getAllPageSlugs();
}
5 changes: 5 additions & 0 deletions app/docs/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import {Leftbar} from '@/components/leftbar';

// Force this layout to be fully static at build time and restrict to generated params
export const dynamic = 'force-static';
export const dynamicParams = false;
export const revalidate = false;

export default function DocsLayout({
children,
}: Readonly<{
Expand Down
Binary file added app/fonts/muoto-bold.woff
Binary file not shown.
Binary file added app/fonts/muoto-regular.woff
Binary file not shown.
8 changes: 4 additions & 4 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ export default function Home() {
server-rendered web app.
</p>
<p>
Zero is currently in public alpha. It's got a few rough edges, and you
have to deploy it yourself, but it's already remarkably fun. We&apos;re
using it ourselves for Zero's{' '}
Zero is currently in public alpha. It&apos;s got a few rough edges, and
you have to deploy it yourself, but it&apos;s already remarkably fun.
We&apos;re using it ourselves for Zero&apos;s{' '}
<a className="underline" href="https://bugs.rocicorp.dev/">
official bug tracker
</a>{' '}
Expand All @@ -170,7 +170,7 @@ export default function Home() {
<br />
<div className="flex w-full justify-center">
<Button variant="primary" size="default" asChild>
<a href="/docs/">Get Started Now</a>
<Link href="/docs/">Get Started Now</Link>
</Button>
</div>

Expand Down
170 changes: 92 additions & 78 deletions assets/search-index.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React, {useEffect} from 'react';
import React from 'react';
import hljs from 'highlight.js';

type CodeBlockProps = {
Expand Down
8 changes: 4 additions & 4 deletions components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default function Search() {
const doc = searchDocs.find(d => d.id === ref);
if (!doc) return null;

let snippet = extractSnippet(doc.content, sanitizedInput);
const snippet = extractSnippet(doc.content, sanitizedInput);

const snippetIndex = doc.content
.toLowerCase()
Expand Down Expand Up @@ -224,7 +224,7 @@ export default function Search() {
}, 20);

return () => clearTimeout(delayDebounce);
}, [searchedInput]);
}, [searchedInput, lunrIndex]);

// Toggle the menu when ⌘K is pressed
useHotkeys(
Expand Down Expand Up @@ -333,13 +333,13 @@ const PreloadCurrentItem = () => {

const activeItem = useMemo(() => {
return searchDocs.find(item => item.id === value);
}, [value, searchDocs]);
}, [value]);

useEffect(() => {
if (activeItem) {
router.prefetch(activeItem.url);
}
}, [activeItem]);
}, [activeItem, router]);

return <></>;
};
1 change: 0 additions & 1 deletion components/sublink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {SheetClose} from '@/components/ui/sheet';
import {icons} from '@/lib/icons';
import {EachRoute} from '@/lib/routes-config';
import {cn} from '@/lib/utils';
import {ChevronRight} from 'lucide-react';
Expand Down
2 changes: 1 addition & 1 deletion components/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as React from 'react';
import {ThemeProvider as NextThemesProvider} from 'next-themes';
import {type ThemeProviderProps} from 'next-themes/dist/types';
import type {ThemeProviderProps} from 'next-themes';

export function ThemeProvider({children, ...props}: ThemeProviderProps) {
return (
Expand Down
1 change: 0 additions & 1 deletion components/ui/ActiveHashLink.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import clsx from 'clsx';
import Link from 'next/link';
import {useEffect, useRef} from 'react';
import useHash from '../hooks/useHash';
Expand Down
4 changes: 2 additions & 2 deletions contents/docs/auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ Any data placed into your JWT (claims) can be used by permission rules on the ba
const isAdminRule = (decodedJWT, {cmp}) => cmp(decodedJWT.role, '=', 'admin');
```

See the [permissions](permissions) section for more details.
See the [permissions](/docs/permissions) section for more details.

## Examples

See [zbugs](samples#zbugs) or [hello-zero](samples#hello-zero).
See [zbugs](/docs/samples#zbugs) or [hello-zero](/docs/samples#hello-zero).
41 changes: 29 additions & 12 deletions contents/docs/custom-mutators.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ You can use the `.client` promise in this case to wait for a write to complete o

```ts
try {
const write = zero.mutate.issue.update({
const write = zero.mutate.issue.insert({
id: 'issue-123',
title: 'New title',
});
Expand All @@ -318,12 +318,25 @@ You can also wait for the server write to succeed:

```ts
try {
await zero.mutate.issue.update({
const write = zero.mutate.issue.insert({
id: 'issue-123',
title: 'New title',
}).server;
});

await write.client;

// issue-123 is written to server
// optimistic write guaranteed to be present here, but not
// server write.
const read1 = await zero.query.issue.where('id', 'issue-123').one();

// Await server write – this involves a round-trip.
await write.server;

// issue-123 is written to server and any results are
// syned to this client.
// read2 could potentially be undefined here, for example if the
// server mutator rejected the write.
const read2 = await zero.query.issue.where('id', 'issue-123').one();
} catch (e) {
console.error('Mutator failed on client or server', e);
}
Expand All @@ -339,20 +352,24 @@ If the client-side mutator fails, the `.server` promise is also rejected with th

You will need a server somewhere you can run an endpoint on. This is typically a serverless function on a platform like Vercel or AWS but can really be anything.

Set the push URL with the [`ZERO_PUSH_URL` env var or `--push-url`](./zero-cache-config#push-url).

If there is per-client configuration you need to send to the push endpoint, you can do that with `push.queryParams`:
Configure the push endpoint with the `push.url` parameter in your `Zero` constructor:

```ts
const z = new Zero({
const zero = new Zero({
push: {
queryParams: {
workspaceID: '42',
},
url: 'https://zero.my-server.com/push',
},
});
```

You will also need to enable the server to be used as a push endpoint with the [`ZERO_PUSH_URL` environment variable or `--push-url` flag](./zero-cache-config#push-url):

```bash
ZERO_PUSH_URL=https://*.my-server.com/push
```

The `ZERO_PUSH_URL` parameter accepts wildcards, enabling the client to pass runtime configuration to the push endpoint or to use a different push endpoint, e.g., for previews. See the [config docs](./zero-cache-config#push-url) for the full syntax.

The push endpoint receives a `PushRequest` as input describing one or more mutations to apply to the backend, and must return a `PushResponse` describing the results of those mutations.

If you are implementing your server in TypeScript, you can use the `PushProcessor` class to trivially implement this endpoint. Here’s an example in a [Hono](https://hono.dev/) app:
Expand Down Expand Up @@ -672,4 +689,4 @@ This will be documented in the future, but you can refer to the [PushProcessor](
## Examples

- Zbugs uses [custom mutators](https://github.com/rocicorp/mono/blob/a76c9a61670cc09e1a9fe7ab795749f3eef25577/apps/zbugs/shared/mutators.ts) for all mutations, [write permissions](https://github.com/rocicorp/mono/blob/a76c9a61670cc09e1a9fe7ab795749f3eef25577/apps/zbugs/shared/mutators.ts#L61), and [notifications](https://github.com/rocicorp/mono/blob/a76c9a61670cc09e1a9fe7ab795749f3eef25577/apps/zbugs/server/server-mutators.ts#L35).
- `hello-zero-solid` uses custom mutators for all [mutations](TODO), and for [permissions](TODO).
- [`hello-zero-solid`](./samples#hello-zero-solid) uses custom mutators for all mutations and for permissions.
2 changes: 1 addition & 1 deletion contents/docs/debug/slow-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ If you are seeing unexpected UI flicker when moving between views, it is likely

You may alternately want to [preload some data](https://zero.rocicorp.dev/docs/reading-data#preloading) at app startup.

Conversely, if you are setting `ttl` to long values, then it can happen that you have many backgrounded queries still running that the app is not using. You can see which queries are running using the [inspector](/docs/inspector). Ensure that only expected queries are running. See [long TTLs](/docs/reading-data#long-ttl-warning) for more information.
Conversely, if you are setting `ttl` to long values, then it can happen that you have many backgrounded queries still running that the app is not using. You can see which queries are running using the [inspector](./inspector). Ensure that only expected queries are running. See [long TTLs](/docs/reading-data#long-ttl-warning) for more information.

## Check Storage

Expand Down
Loading