Skip to content

Commit fe9658c

Browse files
committed
Partial prerendering of public pages for better core web vitals
1 parent 7e2321d commit fe9658c

File tree

10 files changed

+252
-214
lines changed

10 files changed

+252
-214
lines changed

src/lib/stores/app.ts

+21-6
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ export type AppStore = {
66
themeInUse: 'light' | 'dark';
77
};
88

9-
export const app = writable<AppStore>({
9+
const defaultValue = {
1010
theme: 'auto',
1111
themeInUse: 'light'
12-
});
12+
};
13+
14+
export const app = writable<AppStore>(getInitialValue());
1315

1416
export const iconPath = derived(app, ($app) => {
1517
return (name: string, type: 'color' | 'grayscale') => {
@@ -18,9 +20,22 @@ export const iconPath = derived(app, ($app) => {
1820
});
1921

2022
if (browser) {
21-
app.update((n) => ({
22-
...n,
23-
...(JSON.parse(localStorage.getItem('appwrite')) ?? {})
24-
}));
2523
app.subscribe((u) => localStorage.setItem('appwrite', JSON.stringify(u) ?? '{}'));
2624
}
25+
26+
function getInitialValue() {
27+
if (!browser) {
28+
return defaultValue;
29+
}
30+
const localStorageValue = JSON.parse(localStorage.getItem('appwrite')) || {};
31+
const value = { ...localStorageValue, ...defaultValue };
32+
if (value.theme === 'auto') {
33+
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)');
34+
if (darkThemeMq.matches) {
35+
value.themeInUse = 'dark';
36+
} else {
37+
value.themeInUse = '';
38+
}
39+
}
40+
return value;
41+
}

src/routes/+layout.svelte

+10-22
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
window.VERCEL_ANALYTICS_ID = import.meta.env.VERCEL_ANALYTICS_ID?.toString() ?? false;
2323
}
2424
25+
const prerenderedRoutes = ['/login', '/register', '/recover', '/invite'];
26+
if (prerenderedRoutes.some((n) => $page.url.pathname.startsWith(n))) {
27+
loading.set(false);
28+
}
29+
2530
onMount(async () => {
2631
if ($page.url.searchParams.has('migrate')) {
2732
const migrateData = $page.url.searchParams.get('migrate');
@@ -65,14 +70,7 @@
6570
* Handle initial load.
6671
*/
6772
if (!$page.url.pathname.startsWith('/auth') && !$page.url.pathname.startsWith('/git')) {
68-
const acceptedRoutes = [
69-
'/login',
70-
'/register',
71-
'/recover',
72-
'/invite',
73-
'/card',
74-
'/hackathon'
75-
];
73+
const acceptedRoutes = prerenderedRoutes.concat(['/card', '/hackathon']);
7674
if ($user) {
7775
if (
7876
!$page.url.pathname.startsWith('/console') &&
@@ -106,20 +104,10 @@
106104
107105
$: {
108106
if (browser) {
109-
const isCloudClass = isCloud ? 'is-cloud' : '';
110-
if ($app.theme === 'auto') {
111-
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)');
112-
if (darkThemeMq.matches) {
113-
document.body.setAttribute('class', `theme-dark ${isCloudClass}`);
114-
$app.themeInUse = 'dark';
115-
} else {
116-
document.body.setAttribute('class', `theme-light ${isCloudClass}`);
117-
$app.themeInUse = 'light';
118-
}
119-
} else {
120-
document.body.setAttribute('class', `theme-${$app.theme} ${isCloudClass}`);
121-
$app.themeInUse = $app.theme;
122-
}
107+
document.body.setAttribute(
108+
'class',
109+
`theme-${$app.themeInUse} ${isCloud ? 'is-cloud' : ''}`
110+
);
123111
}
124112
}
125113
</script>

src/routes/invite/+page.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const ssr = true;
2+
export const prerender = true;

src/routes/invite/+page.svelte

+52-45
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { browser } from '$app/environment';
23
import { goto } from '$app/navigation';
34
import { base } from '$app/paths';
45
import { Button, Form, FormList, InputChoice } from '$lib/elements/forms';
@@ -43,53 +44,59 @@
4344
<title>Accept invite - Appwrite</title>
4445
</svelte:head>
4546

47+
<!-- if browser checks allow us to partially prerender on the server to improve FCP and LCP -->
4648
<Unauthenticated>
47-
<svelte:fragment slot="title">
48-
{#if !userId || !secret || !membershipId || !teamId}
49-
Invalid invite
50-
{:else}
51-
Invite
52-
{/if}
49+
<svelte:fragment slot="title"
50+
><span
51+
>{#if browser}
52+
{#if !userId || !secret || !membershipId || !teamId}
53+
Invalid invite
54+
{:else}
55+
Invite
56+
{/if}{/if}</span>
5357
</svelte:fragment>
54-
<svelte:fragment>
55-
{#if !userId || !secret || !membershipId || !teamId}
56-
<Alert type="warning">
57-
<svelte:fragment slot="title">The invite link is not valid</svelte:fragment>
58-
Please ask the project owner to send you a new invite.
59-
</Alert>
60-
<div class="u-flex u-main-end u-margin-block-start-40">
61-
<Button href={`${base}/register`}>Sign up to Appwrite</Button>
62-
</div>
63-
{:else}
64-
<p class="text">You have been invited to join a team project on Appwrite</p>
65-
<Form onSubmit={acceptInvite}>
66-
<FormList>
67-
<InputChoice
68-
required
69-
bind:value={terms}
70-
id="terms"
71-
label="terms"
72-
showLabel={false}>
73-
By accepting the invitation, you agree to the <a
74-
class="link"
75-
href="https://appwrite.io/terms"
76-
target="_blank"
77-
rel="noopener noreferrer">Terms and Conditions</a>
78-
and
79-
<a
80-
class="link"
81-
href="https://appwrite.io/privacy"
82-
target="_blank"
83-
rel="noopener noreferrer">
84-
Privacy Policy</a
85-
>.</InputChoice>
86-
87-
<div class="u-flex u-main-end u-gap-12">
88-
<Button secondary href={`${base}/login`}>Cancel</Button>
89-
<Button submit>Accept</Button>
58+
<svelte:fragment
59+
><div>
60+
{#if browser}
61+
{#if !userId || !secret || !membershipId || !teamId}
62+
<Alert type="warning">
63+
<svelte:fragment slot="title">The invite link is not valid</svelte:fragment>
64+
Please ask the project owner to send you a new invite.
65+
</Alert>
66+
<div class="u-flex u-main-end u-margin-block-start-40">
67+
<Button href={`${base}/register`}>Sign up to Appwrite</Button>
9068
</div>
91-
</FormList>
92-
</Form>
93-
{/if}
69+
{:else}
70+
<p class="text">You have been invited to join a team project on Appwrite</p>
71+
<Form onSubmit={acceptInvite}>
72+
<FormList>
73+
<InputChoice
74+
required
75+
bind:value={terms}
76+
id="terms"
77+
label="terms"
78+
showLabel={false}>
79+
By accepting the invitation, you agree to the <a
80+
class="link"
81+
href="https://appwrite.io/terms"
82+
target="_blank"
83+
rel="noopener noreferrer">Terms and Conditions</a>
84+
and
85+
<a
86+
class="link"
87+
href="https://appwrite.io/privacy"
88+
target="_blank"
89+
rel="noopener noreferrer">
90+
Privacy Policy</a
91+
>.</InputChoice>
92+
93+
<div class="u-flex u-main-end u-gap-12">
94+
<Button secondary href={`${base}/login`}>Cancel</Button>
95+
<Button submit>Accept</Button>
96+
</div>
97+
</FormList>
98+
</Form>
99+
{/if}{/if}
100+
</div>
94101
</svelte:fragment>
95102
</Unauthenticated>

src/routes/login/+page.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const ssr = true;
2+
export const prerender = true;

src/routes/login/+page.svelte

+45-38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { browser } from '$app/environment';
23
import { goto, invalidate } from '$app/navigation';
34
import { base } from '$app/paths';
45
import {
@@ -74,49 +75,55 @@
7475
<title>Sign in - Appwrite</title>
7576
</svelte:head>
7677

78+
<!-- if browser checks allow us to partially prerender on the server to improve FCP and LCP -->
7779
<Unauthenticated>
78-
<svelte:fragment slot="title">Sign in</svelte:fragment>
80+
<svelte:fragment slot="title"
81+
>{#if browser}Sign in{/if}</svelte:fragment>
7982
<svelte:fragment>
80-
<Form onSubmit={login}>
81-
<FormList>
82-
<InputEmail
83-
id="email"
84-
label="Email"
85-
placeholder="Email"
86-
autofocus={true}
87-
required={true}
88-
bind:value={mail} />
89-
<InputPassword
90-
id="password"
91-
label="Password"
92-
placeholder="Password"
93-
required={true}
94-
meter={false}
95-
showPasswordButton={true}
96-
bind:value={pass} />
97-
<FormItem>
98-
<Button fullWidth submit {disabled}>Sign in</Button>
99-
</FormItem>
100-
{#if isCloud}
101-
<span class="with-separators eyebrow-heading-3">or</span>
83+
{#if browser}
84+
<Form onSubmit={login}>
85+
<FormList>
86+
<InputEmail
87+
id="email"
88+
label="Email"
89+
placeholder="Email"
90+
autofocus={true}
91+
required={true}
92+
bind:value={mail} />
93+
<InputPassword
94+
id="password"
95+
label="Password"
96+
placeholder="Password"
97+
required={true}
98+
meter={false}
99+
showPasswordButton={true}
100+
bind:value={pass} />
102101
<FormItem>
103-
<Button github fullWidth on:click={onGithubLogin} {disabled}>
104-
<span class="icon-github" aria-hidden="true" />
105-
<span class="text">Sign in with GitHub</span>
106-
</Button>
102+
<Button fullWidth submit {disabled}>Sign in</Button>
107103
</FormItem>
108-
{/if}
109-
</FormList>
110-
</Form>
104+
{#if isCloud}
105+
<span class="with-separators eyebrow-heading-3">or</span>
106+
<FormItem>
107+
<Button github fullWidth on:click={onGithubLogin} {disabled}>
108+
<span class="icon-github" aria-hidden="true" />
109+
<span class="text">Sign in with GitHub</span>
110+
</Button>
111+
</FormItem>
112+
{/if}
113+
</FormList>
114+
</Form>
115+
{/if}
111116
</svelte:fragment>
112117
<svelte:fragment slot="links">
113-
<li class="inline-links-item">
114-
<a href={`${base}/recover`}><span class="text">Forgot Password?</span></a>
115-
</li>
116-
<li class="inline-links-item">
117-
<a href={`${base}/register${$page?.url?.search ?? ''}`}>
118-
<span class="text">Sign Up</span>
119-
</a>
120-
</li>
118+
{#if browser}
119+
<li class="inline-links-item">
120+
<a href={`${base}/recover`}><span class="text">Forgot Password?</span></a>
121+
</li>
122+
<li class="inline-links-item">
123+
<a href={`${base}/register${$page?.url?.search ?? ''}`}>
124+
<span class="text">Sign Up</span>
125+
</a>
126+
</li>
127+
{/if}
121128
</svelte:fragment>
122129
</Unauthenticated>

src/routes/recover/+page.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const ssr = true;
2+
export const prerender = true;

0 commit comments

Comments
 (0)