S
SolidJS•17mo ago
Silverdagger

How can I populate HTML on the server with db data before sending it to the client

Hello - i am new to solidstart and today as I was attempting to port a nextjs app into solidstart I was confused as to how I am supposed to handle data fetching in a page route. I need to render a page for my users on the server. In my specific use case I need some session data in combination with a call to the database. I used the middleware to verify a user is logged in and store the sessiondata into the 'locals' variable which I can access inside routes - but i still haven't found out how to make my async db call to work. I am using the 'isServer' var to run the db call only on the server but The routes themselves are synchronous so I cannot use await there, and when I use createReousrce I get no result on my page. (I know i can use an API call from the client or a server action but that is not the functionality I need)
19 Replies
Tur
Tur•17mo ago
I haven't touched SolidStart but it seems that the problem is in that createResource is asyncronous and you need to put the result to createEffect to catch the result once it has come For example
const [responseData] = createResource(params.itemID, async () => await fetchFromAPI(getURL(), handleResponseError));
//...
createEffect((done) => {
if (!done && !responseData.loading) {
// catch responseData() here
return true;
}
});
const [responseData] = createResource(params.itemID, async () => await fetchFromAPI(getURL(), handleResponseError));
//...
createEffect((done) => {
if (!done && !responseData.loading) {
// catch responseData() here
return true;
}
});
Or you can just handle responseData within JSX because JSX is an Effect itself
Silverdagger
SilverdaggerOP•17mo ago
Figured out how to do it - and it works like below- but now i have another issue -> that being When I navigate to the /profile route via a button I get an empty page.. when I refresh, or use the url bar to navigate to /profile it works as expected. ( i use the <A> tag to navigate - and i can't understand why this happens 😦
export default function MyProfile() {
const navigate = useNavigate()

const [profile] = createResource(async () => {
if (isServer) {
if (useRequest().locals.user === undefined) {
navigate('/login', { replace: true })
return null
}
const user = useRequest().locals.user as { id: number; username: string }
const profile = await prisma.user.findUnique({
select: { id: true, username: true, email: true, createdAt: true },
where: { id: user!.id },
})

if (!profile) {
navigate('/login', { replace: true })
return null
}
console.log(profile)
return profile
}
})

return (
<div class="max-w-screen-xl mx-auto">
<Show when={profile() !== undefined && profile() !== null}>
<ProfileContextProvider profile={profile()}>
<ClientProfilePage />
</ProfileContextProvider>
</Show>
</div>
)
}
export default function MyProfile() {
const navigate = useNavigate()

const [profile] = createResource(async () => {
if (isServer) {
if (useRequest().locals.user === undefined) {
navigate('/login', { replace: true })
return null
}
const user = useRequest().locals.user as { id: number; username: string }
const profile = await prisma.user.findUnique({
select: { id: true, username: true, email: true, createdAt: true },
where: { id: user!.id },
})

if (!profile) {
navigate('/login', { replace: true })
return null
}
console.log(profile)
return profile
}
})

return (
<div class="max-w-screen-xl mx-auto">
<Show when={profile() !== undefined && profile() !== null}>
<ProfileContextProvider profile={profile()}>
<ClientProfilePage />
</ProfileContextProvider>
</Show>
</div>
)
}
My data also populates the page on refresh
Martnart
Martnart•17mo ago
You now have converted your data to a "server-only" resource, afaict. Your whole resource fetcher is wrapped in if (isServer) so on client-side navigation this whole block will not execute. Easy way from here is assume that a User is logged in and do the fetch also if (!isServer)
Silverdagger
SilverdaggerOP•17mo ago
If I don't use the 'isServer' the code executes in both client and server which is not the behavior I need. I want to query the db - build the page and ship it as html. How can I achieve this?
Martnart
Martnart•17mo ago
You can use routeData with createServerData$ This is a callback that will only execute on the server. Keep in mind that SSR only applies for your first page-load. After that everything will switch to CSR (client-side rendering) since the internal processes will take over after the initial hydration.
Silverdagger
SilverdaggerOP•17mo ago
This does a post request to fetch the data, I need the data to render on the server and be sent as html.
Tommypop
Tommypop•16mo ago
Is there any reason why you need the HTML specifically? If the page is SSRed, then the server$ callback will be called on the server and used to populate the HTML on the server.
Silverdagger
SilverdaggerOP•16mo ago
It matches the usecase of the app I am trying to port from NextJS (one call to fetch the html instead of 2 for html+data) I selected SSR in the initialization options (althought it doesn't state anything about it in the vite config i believe that it is because its true by default) -> the server$ from what I saw does a post request fetching the data and I want to avoid having extra requests when the data doesn't require reactivity It seems like such a simple task I am ashamed to admit I can't figure out how to make it work
Tommypop
Tommypop•16mo ago
The post request should only be made if you're accessing the data in CSR afaik Like if you're calling the server$ callback in SSR, no extra request should get made The better approach is probably using routeData with createServerData$ because that'll fetch the data in parallel to your route loading even if you navigate to the page on the client
Silverdagger
SilverdaggerOP•16mo ago
Alright I made a bit of progress thanks to your input. The behaviour is as you described (if i navigate directly to /profile I get the page without any additional request made which is what i want ) but the data still gets fetched via post if I navigate to the same /profile route using a link. To be more specific, if i navigate using <a> it works as expected but page fully reloads , on the other hand using <A> a post request is sent Is there a way to specify that I need the data only as html and resolve all resources before sending it to the client? I paste my code below if it helps (maybe I did something different than what you described)
import ClientProfilePage from '~/components/clientprofilepage'
import { ProfileContextProvider } from '~/components/profileprovider'
import { prisma } from '~/lib/prismaclient'
import {
ErrorBoundary,
RouteDataArgs,
useNavigate,
useRouteData,
} from 'solid-start'
import { createServerData$, redirect } from 'solid-start/server'
import { Show } from 'solid-js'
import { getUser } from '~/lib/sessions'

export function routeData({ params }: RouteDataArgs) {
return createServerData$(async (_, { request }) => {
const user = await getUser(request)
if (user === null) return redirect('/login')
const profile = await prisma.user.findUnique({
select: { id: true, username: true, email: true, createdAt: true },
where: { id: user!.id },
})

if (!profile) {
return redirect('/login')
}
return profile
})
}

export default function MyProfile() {
const navigate = useNavigate()
console.log('myprofile called')
const profile = useRouteData()

return (
<div class="max-w-screen-xl mx-auto">
<ErrorBoundary>
<Show when={profile() && !profile.loading} fallback={'Waiting...'}>
<ProfileContextProvider profile={profile()}>
<ClientProfilePage />
</ProfileContextProvider>
</Show>
</ErrorBoundary>
</div>
)
}
import ClientProfilePage from '~/components/clientprofilepage'
import { ProfileContextProvider } from '~/components/profileprovider'
import { prisma } from '~/lib/prismaclient'
import {
ErrorBoundary,
RouteDataArgs,
useNavigate,
useRouteData,
} from 'solid-start'
import { createServerData$, redirect } from 'solid-start/server'
import { Show } from 'solid-js'
import { getUser } from '~/lib/sessions'

export function routeData({ params }: RouteDataArgs) {
return createServerData$(async (_, { request }) => {
const user = await getUser(request)
if (user === null) return redirect('/login')
const profile = await prisma.user.findUnique({
select: { id: true, username: true, email: true, createdAt: true },
where: { id: user!.id },
})

if (!profile) {
return redirect('/login')
}
return profile
})
}

export default function MyProfile() {
const navigate = useNavigate()
console.log('myprofile called')
const profile = useRouteData()

return (
<div class="max-w-screen-xl mx-auto">
<ErrorBoundary>
<Show when={profile() && !profile.loading} fallback={'Waiting...'}>
<ProfileContextProvider profile={profile()}>
<ClientProfilePage />
</ProfileContextProvider>
</Show>
</ErrorBoundary>
</div>
)
}
Tommypop
Tommypop•16mo ago
When you navigate through an <A> tag, the navigation happens completely on the client so the server doesn't typically do anything at all. The post request is just getting that data and only that data from the server to the client, so sending HTML isn't really necessary here So no resources are resolved on the server when you make navigations after the initial page load
Silverdagger
SilverdaggerOP•16mo ago
You are correct - I falsely assumed you were getting back a document on each route.. So essentially the server sends the entire app on the initial load and then the client takes over (working as a CSR from then on?)
Tommypop
Tommypop•16mo ago
Yep, exactly :) Although the server doesn't usually send the entire app Route are generally code split So, when you navigate to a route in CSR, the javascript required for that route to work is loaded Using routeData means that your data fetching happens in parallel to this javascript loading, so you don't get long waterfalls
Silverdagger
SilverdaggerOP•16mo ago
Alright from what I can tell - navigating from / to /profile atm gets me this routeData being the slowest to arrive since it first fetches the tsx and then does the post request
No description
Tommypop
Tommypop•16mo ago
profile.tsx and your routeData look like they start being fetched at the same time The delay before your routeData arrives is probably just delay from communicating with the database
Silverdagger
SilverdaggerOP•16mo ago
Ok so basically me changing into what I want will basically have no real effect since it fetches both document and data at the same time and instead of merging them at the server it does so in the client right?
Tommypop
Tommypop•16mo ago
There are no document fetches The profile fetch is the javascript for your profile page So the server doesn't generate any HTML here You want as much to happen on the client as possible for subsequent navigations because round trips to the server are expensive And the profile page javascript can be cached
Silverdagger
SilverdaggerOP•16mo ago
Alright I see - so it's fine as it is now - Thanks a lot for taking the time to explain & walk me through this
Tommypop
Tommypop•16mo ago
np :)
Want results from more Discord servers?
Add your server