Sveltekit and SSR - server-side-rendering

I need a bit of help understanding SSR in the context of sveltekit. I noticed that the load method is called both on the server and the client and I cannot wrap my head around this. I guess it is needed to initialize the state of the client-side component, but why not just pass the props resulting from the SSR to the client?
What if a database request needs to be done during SSR? Now that same database request is repeated from the client? What if that is not even possible? I understand that I can use browser from $app/env to run different code on the server and in the browser but what props do I return? Is there any way to pass data from the server-side invocation of load to the client-side invocation?

why not just pass the props resulting from the SSR to the client?
In order to do this, the props need to be serializable. You couldn't — for example — do something like this:
<script context="module">
export async function load({ fetch }) {
const data = await fetch('/data.json').then(r => r.json());
const model = create_model(data);
return {
props: { model }
};
}
</script>
<script>
export let model;
</script>
<h1>{$model.title}</h1>
Or you might need to dynamically import one component in one case, and a different component in another case, and pass that through as a prop.
There's another disadvantage to serializing the load output (which is what happened with SvelteKit's predecessor, Sapper) — in some cases, you might end up serializing a lot more data than you need to:
<script context="module">
export async function load({ fetch }) {
const compressed = await fetch('/compressed-data.json').then(r => r.json());
const data = decompress(compressed);
return {
props: { data }
};
}
</script>
So SvelteKit runs load on both server and client. But it doesn't mean you're making unnecessary network requests. Anything you fetch in your load function is baked into the server-rendered HTML, meaning a) everything is contained in one request, b) the data used for server and client renders is guaranteed to be consistent, and c) any strings that appear in the fetched data and also appear in the markup are essentially 'free' because of gzip (or brotli).
What if a database request needs to be done during SSR?
You shouldn't be talking directly to the database in load, you should be creating an endpoint and requesting data with fetch. (We may add a method for auto-generating these endpoints in future, but it's not currently on the roadmap.)

Related

Using vuex with Vue 3

Last year I spent some time learning Vue 2. I really enjoyed the framework but did not move forward with a project. I now have time for a project but I'd like to use Vue 3 with the composition API. I'll be using Firebase as the backend. I've seen conflicting techniques on whether or not to use Vuex.
For example, I'd like to store a "currentUser" object in global state that can be accessed from any component in the app. Normally, it would be done using Vuex. I'd have a getter to return the object, an async action to get the data from firebase and a mutation to set the state.
However, I've seen several Vue 3 code examples that do not use Vuex at all, instead they do something like this to get/set a currentUser in an app wherever it is needed for example in a navbar component.
composables/getUser.js
import { ref } from 'vue'
import firebase from 'firebase/app'
// refs
const user = ref(firebase.auth().currentUser)
// auth changes
firebase.auth().onAuthStateChanged(_user => {
console.log('User state change. Current user is:', _user)
user.value = _user
});
const getUser = () => {
return { user }
}
export default getUser
With this little bit of code above, I'm able to import getUser.js and access the currently logged in user using code like this. Also, the user object is now reactive:
<script>
import getUser from '../composables/getUser'
export default {
setup() {
const { user } = getUser()
return { user }
}
}
</script>
It seems I can use these little functions to get data from db directly without the need to use the Vuex pattern, which I find to be a bit more complicated.
So, my question is - if I'm starting a new Vue 3 project, is it ok to create "composable" functions to get/set data and import them into my components instead of using Vuex? Are there any downsides to this method? Or should I just stick with Vuex?
Short answer - You don't need it.
Long answer - It depends.
It depends mostly on your app and how often do you use "currentUser" data inside your components. If it's in 2 or more components, how often do you want to perform actually fetching from backend?
Once on app-init/login or every time each component mounts?
(probably once)
Does it need to be reactive? If yes - then you'll probably use centralized data pattern, your own or a library. Taken that into consideration it's probably more simple to just use Vuex.

Why can't I use QueryCache to get at "cache" from queryClient.prefetchQuery

So, I am trying to access the cache that I set with: queryClient.prefetchQuery, in a SSR page.
I am using this on the SSR side that I hydrate:
ala:
await queryClient.prefetchQuery(['userSesion'], () => fetchUserSession());
// This is wrapping my _app.js file
<QueryClientProvider client={queryClient}>
<Hydrate state={props.dehydratedState}>
Then on the client side, I am trying to get at this "cache".
const queryCache = new QueryCache();
queryCache.find("userSession"); // <--- This is undefined/empty
Also tried: no luck
queryClient.getQueryData('userSession'))
queryClient.getQueryState('userSession'))
Mind you, I "SEE" the data/cache in the tools, so why can't I get at it?
because you create a new Cache, and that cache has no connection to the queryClient. If you want to get the cache, you can do this via:
const queryClient = useQueryClient()
const queryCache = queryClient.getQueryCache()
that being said, there should be rarely the need to interact with the QueryCache directly. The QueryClient exposes functions to interact with the cache, and if you have a component that needs data from the cache, it's always preferred to just call useQuery in that component. It will give you data from the cache instantly if there is any, and perform a background refetch if the data is considered stale.

How can I ensure my not yet fetched data doesn't break my site

I am currently building a like button on my card component in vue. I am fetching data from firebase using middleware on a page to dispatch the vuex action that will go and get my user info which has their liked_posts stored in an array.
The issue comes up that when I load a page requiring some of the data
i.e. liked_posts and my state is empty it throws a error of
"undefined".
How can I make sure that even if the user hasn't signed in or hasn't ever visited that my user data wont cause an error
I have tried to change my action in the Vuex store to be asynchronous and use await so that I made sure the data was there, but it didn't help.
What is happening is the below code in computed properties is trying to access an object that doesn't exist in the array yet.
likedOrNot() {
const likeInfo = this.$store.state.userInfoSub[0].liked_posts
return likeInfo.includes(this.$store.state.loadedCards[0].id)
}
This data isn't there yet because the user isn't signed in, exist ect. once they do and middleware is dispatching an action to fetch the user data the userInfoSub will be filled with info.
my base state looks like this when the user hasn't signed in or middleware hasnt fired to look for the user that gets put in cookies.
So I need away to ensure my lack of userInfoSub doesn't break my computer property
loadedCards:Array[1]
0:Object
token:null
user:null
userInfoSub:Array[0]
username:null
Here's an opinionated answer: use get from lodash.
npm i lodash
Then you can write something like this:
import get from 'lodash/get';
export default {
computed: {
isLiked() {
const cardId = get(this.$store, 'state.loadedCards[0].id');
const postIds = get(this.$store, 'state.userInfoSub[0].liked_posts', []);
return postIds.includes(cardId);
},
},
};

Send a constant from server to client in meteorjs

Scenario: You want to put the build id in the footer of a meteor app. The client will need a reference to process.env.BUILD_ID from the server.
Common answer: Save it in a collection. On meteor start, save the current build id to a collection and load that on the client.
Problem with the common answer: What if you have multiple containers running? Say for instance you're using kube to update the version. You have version abc running, and then kube starts pushing up def. In the process of starting each def container, it sets the current version in the db to def. Yet the abc containers are still running. If you hit one of those containers, it will report def as the build id. Even though thats wrong. Additionally, what if the kube update fails, and you decide to cancel the push to the new version and keep the old containers running. Until the next container restart, youll be serving the abc content but telling the user its version def.
What would be the proper way to store a constant and match it on the client without lying about its true value?
Meteor methods are the way to get data from the server (as opposed to the using publications to get from the database). I recommend using ValidatedMethod for extra modularity and nicer validation.
Define the following method in your project and make sure it is imported to the server on server startup:
import { Meteor } from 'meteor/meteor';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
const getAppVersion = new ValidatedMethod({
name: 'getAppVersion',
validate: null,
run({}) {
return process.env.BUILD_ID;
},
});
export default getAppVersion;
Then in the frontend code:
import getAppVersion from '/imports/api/methods/getAppVersion.js';
getAppVersion.call({}, (err, res) => {
if (res) setFooterVersion(res);
});
You can throw it in a store:
// in store.js
export default const store = {
buildId: '',
};
// then in buildId.js:
import store from './store';
Meteor.call('getBuildId', (res, err) => store.buildId = res);
// in someplace like server/methods.js:
Meteor.methods({
'getBuildId'() {
return process.env.BUILD_ID;
},
});
// then, anywhere you need the build id:
import store from './store';
store.buildId; // outputs build id
If you need to make it reactive, you can make the store object a ReactiveDict, then put the Meteor.call part within Tracker.autorun for what you want it to react to. You can alternatively put it in something like a setInterval to run every 10 minutes or so. Of course, you can always store it in the db too -- but for your scenario, you can try to create an additional column like kubeNode and pass in the server id from kubernetes into the db, so when you call it again, you call it with a reference to the appropriate k8s node.

Should calculations be stored in Redux store if those are to be submitted in a request?

I have a form that asks the user to type in some numbers. Then, some calculations are made using those numbers that are stored in a Redux store. Right now I do not store the calculations in the Redux store, but just do them inside the render function of my component.
Now I need the submitted form to include those calculated values in my HTTP request. How should I go about it? Should I make the Redux store hold the calculations?
If you plan on using the raw values as well as the calculated values in your UI, I can see a benefit to keeping both inside of your redux store.
If you only need to use the calculation when you make the HTTP request, and the calculated data is not being used as a part of the UI, a cleaner implementation might be to separate the function that does the calculation into a utility file, and importing and using the function in the component/container or actions file that the calculation is needed.
I think this would create a clearer separation of concerns in each file, and the calculation utility could be used in multiple files if necessary.
// Utility file
export const calculationFunction = (someParam) => {
\\ Calculation here
return yourAnswer
}
then
// Actions File (Note this is how my action dispatch is set up, yours might look a bit different
import { calculationFunction } from 'utilities/calculations';
export const sendData = (data) => ({
type: types.SEND_DATA,
responseTypes: [types.SEND_DATA_SUCCESS, types.SEND_DATA_FAILURE],
promise: (client: any) => client.post('/data', calculationFunction(data)),
});

Resources