Public and Private Data
Combine identity-independent SSR content with authenticated user data on one page.
Result
Public article content renders for everyone and never waits for auth. A signed-in bookmark state uses identity and remains absent for anonymous users.
<script setup lang="ts">
import { api } from '#convex/api'
const route = useRoute()
const slug = computed(() => String(route.params.slug))
const { data: article } = await useConvexQuery(
api.articles.bySlug,
computed(() => ({ slug: slug.value })),
{ auth: 'none' },
)
const bookmarkArgs = computed(() => (article.value ? { articleId: article.value._id } : 'skip'))
const { data: bookmark, status: bookmarkStatus } = await useConvexQuery(
api.bookmarks.forArticle,
bookmarkArgs,
{ auth: 'required', server: false },
)
</script>
<template>
<article v-if="article">
<h1>{{ article.title }}</h1>
<ArticleBody :content="article.body" />
<ConvexAuthenticated>
<BookmarkButton :bookmarked="Boolean(bookmark)" :loading="bookmarkStatus === 'pending'" />
</ConvexAuthenticated>
</article>
</template>Why the modes differ
articles.bySlug uses none: it never waits for auth and never receives identity. This makes its SSR result safe to reason about as public content.
bookmarks.forArticle uses required: it stays idle while anonymous and executes with identity after sign-in. server: false keeps private bookmark state out of the initial HTML for this product choice.
Security boundary
The bookmark query and mutation verify identity and article access. <ConvexAuthenticated> only controls rendering.
Caching
Public article HTML may be cacheable according to application policy because the public query is identity-independent. Do not include private bookmark data in that shared cache.