feat: SSR shopping list by fetching initial data server-side
All checks were successful
CI / update (push) Successful in 3m33s
All checks were successful
CI / update (push) Successful in 3m33s
Server load now fetches the shopping list from the DB and passes it as initialList. The sync layer seeds state immediately in the script block (not onMount) so SSR renders the full list. SSE connects client-side in onMount for real-time updates.
This commit is contained in:
@@ -166,6 +166,20 @@ export function createShoppingSync() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Seed state from server-loaded data (safe to call during SSR). */
|
||||
function seed(initialList: { version: number; items: ShoppingItem[] }, token?: string | null) {
|
||||
if (token) shareToken = token;
|
||||
version = initialList.version;
|
||||
items = initialList.items || [];
|
||||
status = 'synced';
|
||||
}
|
||||
|
||||
/** Connect SSE for real-time updates (client-only, call in onMount). */
|
||||
function connect(token?: string | null) {
|
||||
if (token) shareToken = token;
|
||||
connectSSE();
|
||||
}
|
||||
|
||||
function addItem(name: string, user: string, category = 'Sonstiges') {
|
||||
items = [...items, {
|
||||
id: generateId(),
|
||||
@@ -211,6 +225,8 @@ export function createShoppingSync() {
|
||||
get version() { return version; },
|
||||
apiUrl,
|
||||
init,
|
||||
seed,
|
||||
connect,
|
||||
addItem,
|
||||
toggleItem,
|
||||
removeItem,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { getShoppingUser } from '$lib/server/shoppingAuth';
|
||||
import { dbConnect } from '$utils/db';
|
||||
import { ShoppingList } from '$models/ShoppingList';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals, url }) => {
|
||||
const session = await locals.auth();
|
||||
@@ -9,9 +11,24 @@ export const load: PageServerLoad = async ({ locals, url }) => {
|
||||
// Allow access with valid share token even without session
|
||||
if (!session && token) {
|
||||
const user = await getShoppingUser(locals, url);
|
||||
if (user) return { session: null, shareToken: token };
|
||||
if (user) {
|
||||
await dbConnect();
|
||||
const list = await ShoppingList.findOne().lean();
|
||||
return {
|
||||
session: null,
|
||||
shareToken: token,
|
||||
initialList: list ? { version: list.version, items: list.items } : { version: 0, items: [] }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!session) throw redirect(302, '/login');
|
||||
return { session, shareToken: null };
|
||||
|
||||
await dbConnect();
|
||||
const list = await ShoppingList.findOne().lean();
|
||||
return {
|
||||
session,
|
||||
shareToken: null,
|
||||
initialList: list ? { version: list.version, items: list.items } : { version: 0, items: [] }
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
let isGuest = $derived(!data.session);
|
||||
const sync = getShoppingSync();
|
||||
|
||||
// Seed sync state immediately so SSR can render the list
|
||||
if (data.initialList) {
|
||||
sync.seed(data.initialList, data.shareToken);
|
||||
}
|
||||
|
||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
||||
const loc = $derived(locale(lang));
|
||||
|
||||
@@ -141,7 +146,13 @@
|
||||
let checkedCount = $derived(sync.items.filter(i => i.checked).length);
|
||||
let totalCount = $derived(sync.items.length);
|
||||
|
||||
onMount(() => { sync.init(shareToken); });
|
||||
onMount(() => {
|
||||
if (data.initialList) {
|
||||
sync.connect(shareToken);
|
||||
} else {
|
||||
sync.init(shareToken);
|
||||
}
|
||||
});
|
||||
onDestroy(() => { sync.disconnect(); });
|
||||
|
||||
async function addItem() {
|
||||
|
||||
Reference in New Issue
Block a user