How to ideally protecting routes with Layout

I was using Solid Start with auth js, everything fine. But when i look the server log, it was called twice.
[login]-> [getSession & wrap in server fn with key]-> [call in layout]-> [also call in page]-> [called twice]
[login]-> [getSession & wrap in server fn with key]-> [call in layout]-> [also call in page]-> [called twice]
What i expect the server fn will call once and use cache for the rest. Here's my code:
// server.ts
export const authorize = query(async ()=> {
"use server"

const req = getRequestEvent()?.request // solid/web
const sesh = await getSession(req, authOptions) // auth-js

// this called twice
console.log(sesh)

if(!sesh) throw redirect("/login")
return sesh
},"sesh")

export const getPosts = query(async ()=> {
"use server"

await authorize()

const someQuery = await db.query()

return someQuery
}, "posts")

export const getPostById = query(async (id: string)=> {
"use server"

await authorize()

const someQuery = await db.query(id)

return someQuery
}, "post-by-id")
// server.ts
export const authorize = query(async ()=> {
"use server"

const req = getRequestEvent()?.request // solid/web
const sesh = await getSession(req, authOptions) // auth-js

// this called twice
console.log(sesh)

if(!sesh) throw redirect("/login")
return sesh
},"sesh")

export const getPosts = query(async ()=> {
"use server"

await authorize()

const someQuery = await db.query()

return someQuery
}, "posts")

export const getPostById = query(async (id: string)=> {
"use server"

await authorize()

const someQuery = await db.query(id)

return someQuery
}, "post-by-id")
// some-layout.tsx
export route = {
preload: ()=> {
authorize()
}
}

// this called once
export default function Layout(){
const sesh = createAsync(()=> authorize())
}
// some-layout.tsx
export route = {
preload: ()=> {
authorize()
}
}

// this called once
export default function Layout(){
const sesh = createAsync(()=> authorize())
}
// some-layout/random-post.tsx
export route = {
preload: ({ location })=> {
const id = ...

getPostById(id)
getPosts()
}
}

// this called once
export default function PostPage(props){
const posts = createAsync(()=> getPosts())
const post = createAsync(()=> getPostById(props.etc.id))
}
// some-layout/random-post.tsx
export route = {
preload: ({ location })=> {
const id = ...

getPostById(id)
getPosts()
}
}

// this called once
export default function PostPage(props){
const posts = createAsync(()=> getPosts())
const post = createAsync(()=> getPostById(props.etc.id))
}
But if this common behavior, it is unnecessary?
2 Replies
peerreynders
peerreynders4w ago
But when i look the server log, it was called twice.
query is a client side mechanism, not a server side one. So the short term cache effect only works client side. On the server things tend to be scoped by request. When authorize() is called in getPosts and getPostsById it's just going to run again.
In fact I wouldn't be comfortable with the
await authorize() // this is a `query` wrapper
await authorize() // this is a `query` wrapper
in getPosts and getPostById. To start I'd go with something like
// file: session.ts
// This module contains no client side code
export async function getSessionOrRedirect() {
const req = getRequestEvent()?.request; // solid/web
const sesh = await getSession(req, authOptions); // auth-js

console.log(sesh);

if (!sesh) throw redirect('/login');
return sesh;
}
// file: session.ts
// This module contains no client side code
export async function getSessionOrRedirect() {
const req = getRequestEvent()?.request; // solid/web
const sesh = await getSession(req, authOptions); // auth-js

console.log(sesh);

if (!sesh) throw redirect('/login');
return sesh;
}
// file: server.ts
import { getSessionOrRedirect } from './session.js';

export const authorize = query(async () => {
'use server';
return getSessionOrRedirect();
}, 'sesh');

export const getPosts = query(async () => {
'use server';

await getSessionOrRedirect();

const someQuery = await db.query();

return someQuery;
}, 'posts');

export const getPostById = query(async (id: string) => {
'use server';

await getSessionOrRedirect();

const someQuery = await db.query(id);

return someQuery;
}, 'post-by-id');
// file: server.ts
import { getSessionOrRedirect } from './session.js';

export const authorize = query(async () => {
'use server';
return getSessionOrRedirect();
}, 'sesh');

export const getPosts = query(async () => {
'use server';

await getSessionOrRedirect();

const someQuery = await db.query();

return someQuery;
}, 'posts');

export const getPostById = query(async (id: string) => {
'use server';

await getSessionOrRedirect();

const someQuery = await db.query(id);

return someQuery;
}, 'post-by-id');
Then I'd use locals to associate the session with the RequestEvent.
async function getSessionOrRedirect() {
const event = getRequestEvent()?.request;
if (event) {
if (event.locals.session) return event.locals.session;

const session = await getSession(event, authOptions);
console.log(session);
if (session) return (event.locals.session = session);
}

throw redirect('/login');
}
async function getSessionOrRedirect() {
const event = getRequestEvent()?.request;
if (event) {
if (event.locals.session) return event.locals.session;

const session = await getSession(event, authOptions);
console.log(session);
if (session) return (event.locals.session = session);
}

throw redirect('/login');
}
And even that will not eliminate repeated execution across requests. authorize(), getPostById, and getPosts() are still three separate requests which run independently on the server. The preload just runs them as soon as the cursor hovers over the link to navigate to the route. It's the results of authorize(), getPostById, and getPosts() that query caches (for a short time) on the client side (i.e. with no impact on the server side).
haerinn
haerinnOP4w ago
Thank you for making it more clear with the better understanding. Unfortuntly i've tried and because im using getSession() from @auth/core/.. , so didnt really get the benefit from the locals. ( Might be using the useSession() from vinxi soon ). I already used the "no" wrapper query authorize(), but for testing purpose i found interesting case (for me) when calling both getPostById() & getPosts() with the query (🧙‍♂️) idea at the same time, the log run once n that feels strange. But i got the point, sorry too much editing (my bad)

Did you find this page helpful?