Server-Side Rendering(SSR)
Overview
Although the positioning of alova is not to make requests in nodejs, we have also adapted it in order to combine the server-side rendering(Nuxt3.x / Nextjs / sveltekit) of the UI framework. Although built-in request functionality is provided in e.g. Nuxt3.x
, Sveltekit
, if you choose to use alova, you can use alova to manage requests in both server and client, instead of server and client separately. Use different request schemes to manage them.
Here are some caveats for using alova in SSR, and examples of usage in SSR for different UI frameworks.
Call apis on server
In SSR, it is necessary to get data on server and render it into HTML. In this case, we cannot use alova's use hooks (and do not need to use them) to obtain data. Below we will show the supported SSR frameworks respectively.
Nuxt3.x
In Nuxt3.x, useAsyncData
is provided to initialize page data on server, and useFetch
and $fetch
request functions are also provided. These request functions that can be used on both server and client are really convenient. However, if you want to use alova in nuxt, you can use the combination of useAsyncData + alova.Method to complete the server-side data fetching, which is no different from your usual useAsyncData
.
<script setup>
const todoListGetter = alovaInstance.Get('/todo/list', {
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
// return promise in useAsyncData
const { data, pending, refresh } = useAsyncData(async () => {
return todoListGetter.send();
});
</script>
Nextjs
- App Router
- Pages Router
In nextjs app router mode, you can use method instance directly in the async component.
const App = async () => {
const data = await alovaInstance.Get('/todo/list', {
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
// then ...code
return data.map(item => (
<div>
<span>{item.title}</span>
<span>{item.time}</span>
</div>
));
};
export default App;
In tranditional pages router mode, nextjs providers fixed server-side initialization page data functions, such as getStaticProps
, getServerSideProps
and getStaticPaths
, etc., you can directly use the method instance call apis in the function.
const todoListGetter = alovaInstance.Get('/todo/list', {
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
export const getServerSideProps = async ctx => {
const list = await todoListGetter.send();
return {
props: {
list
}
};
};
export default function App(props) {
return props.list.map(item => (
<div>
<span>{item.title}</span>
<span>{item.time}</span>
</div>
));
}
Sveltekit
Sveltekit also provides the load
function to initialize the page data on server, and you can also directly use the method instance call apis in the function. For example, call apis in +page.server.js
.
const todoListGetter = alovaInstance.Get('/todo/list', {
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
list: todoListGetter
};
}
Using usehooks in SSR
Since each SSR framework has its own way to initialize data on server, when generating html in SSR, useRequest
and useWatcher
in the component will not be initiated even if immediate
is set to true
request, as this is more like client initialization data.
However, if you need to initialize the data of the page as in the client, you can also set immediate
to true
, and when the page is running in the browser, you can use all the functions of alova as usual.
Precautions
Client and server caches are separate
If you use alova's caching function, you may need to pay attention here that the client and server caches are not shared, which means that if you directly use usehooks to get data when initializing the page, you may Run into inconsistencies in client-side and server-side rendering, although few people do.
Please see the following code snippet.
- nuxt
- next
- sveltekit
<template>
<div v-if="loading">loading</div>
<div>{{ data }}</div>
</template>
<script setup>
const { loading, data } = useRequest(alovaGetter);
</script>
function App(props) {
const { loading, data } = useRequest(alovaGetter);
return (
<>
{loading ? <div>loading</div> : null}
<div>{data}</div>
</>
);
}
<script>
export let data;
const { loading, data } = useRequest(alovaGetter);
</script>
{#if $loading}
<div>loading</div>
{/if}
<div>{{ data }}</div>
The following code assumes that alovaGetter
requests are cached on server, but not on the client.
At this time, when the html is generated on server , loading
is false
and does not display <div>loading</div>
because it hits the cache, but when the client is initialized, because it misses the cache, loading
is true
will cause <div>loading</div>
to be displayed, and the SSR framework will prompt that the rendering of the two ends is inconsistent.
Solution
- Try to put the page data initialization work in the acquisition function instead of the component;
- If you must do this, you can avoid using the same apis on the client and server, or turn off the problematic api caches;
- If caching is also required, you can clear the cache on server in the data initialization function of server. The sample code is as follows:
- nuxt
- next
- sveltekit
<template>
<div v-if="loading">loading</div>
<div>{{ data }}</div>
</template>
<script setup>
import { invalidateCache } from 'alova';
const { loading, data } = useRequest(alovaGetter);
// Clear the cache on server
useAsyncData(
() => {
invalidateCache(alovaGetter);
},
{
server: true
}
);
</script>
import { invalidateCache } from 'alova';
function App(props) {
const { loading, data } = useRequest(alovaGetter);
return (
<>
{loading ? <div>loading</div> : null}
<div>{data}</div>
</>
);
}
export const getServerSideProps = async () => {
// Clear the cache on server
invalidateCache(alovaGetter);
return {
props: {}
};
};
import { invalidateCache } from 'alova';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
// Clear the cache on server
invalidateCache(alovaGetter);
return {};
}