Skip to content

Commit 1512288

Browse files
authored
Merge pull request #284 from connorabbas/ssr-styled-mode-testing
SSR
2 parents fa60e4c + b10ad22 commit 1512288

File tree

15 files changed

+1222
-750
lines changed

15 files changed

+1222
-750
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/.phpunit.cache
22
/node_modules
3+
/bootstrap/ssr
34
/public/build
45
/public/hot
56
/public/storage
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
6+
7+
class EncryptCookies extends Middleware
8+
{
9+
/**
10+
* The names of the cookies that should not be encrypted.
11+
*
12+
* @var list<string>
13+
*/
14+
protected $except = [
15+
'colorScheme'
16+
];
17+
}

app/Http/Middleware/HandleInertiaRequests.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Http\Request;
66
use Inertia\Middleware;
7+
use Tighten\Ziggy\Ziggy;
78

89
class HandleInertiaRequests extends Middleware
910
{
@@ -37,6 +38,11 @@ public function share(Request $request): array
3738
{
3839
return [
3940
...parent::share($request),
41+
'colorScheme' => fn () => $request->cookie('colorScheme', 'auto'),
42+
'ziggy' => fn () => [
43+
...(new Ziggy())->toArray(),
44+
'location' => $request->url(),
45+
],
4046
'auth' => [
4147
'user' => $request->user(),
4248
],

bootstrap/app.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<?php
22

3+
use App\Http\Middleware\EncryptCookies;
34
use App\Http\Middleware\HandleInertiaRequests;
5+
use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncryptCookies;
46
use Illuminate\Foundation\Application;
57
use Illuminate\Foundation\Configuration\Exceptions;
68
use Illuminate\Foundation\Configuration\Middleware;
@@ -16,10 +18,15 @@
1618
health: '/up',
1719
)
1820
->withMiddleware(function (Middleware $middleware) {
19-
$middleware->web(append: [
20-
HandleInertiaRequests::class,
21-
AddLinkHeadersForPreloadedAssets::class,
22-
]);
21+
$middleware->web(
22+
append: [
23+
HandleInertiaRequests::class,
24+
AddLinkHeadersForPreloadedAssets::class,
25+
],
26+
replace: [
27+
BaseEncryptCookies::class => EncryptCookies::class
28+
],
29+
);
2330
})
2431
->withExceptions(function (Exceptions $exceptions) {
2532
$exceptions->respond(function (Response $response, Throwable $exception, Request $request) {

package-lock.json

Lines changed: 943 additions & 641 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,10 @@
22
"private": true,
33
"type": "module",
44
"scripts": {
5+
"build": "vite build && vite build --ssr",
56
"dev": "vite",
6-
"build": "vite build",
77
"lint": "eslint . --fix"
88
},
9-
"devDependencies": {
10-
"@eslint/js": "^9.18.0",
11-
"@typescript-eslint/eslint-plugin": "^8.19.1",
12-
"@typescript-eslint/parser": "^8.19.1",
13-
"@vue/eslint-config-typescript": "^14.5.0",
14-
"eslint": "^9.18.0",
15-
"eslint-config-prettier": "^9.1.0",
16-
"eslint-plugin-vue": "^9.32.0",
17-
"typescript-eslint": "^8.19.1",
18-
"vue-tsc": "^2.2.8"
19-
},
209
"dependencies": {
2110
"@inertiajs/vue3": "^2.0.5",
2211
"@primeuix/themes": "^1.0.0",
@@ -25,7 +14,9 @@
2514
"@types/lodash-es": "^4.17.12",
2615
"@types/qs": "^6.9.18",
2716
"@vitejs/plugin-vue": "^5.2.3",
17+
"@vue/server-renderer": "^3.5.14",
2818
"@vueuse/core": "^13.0.0",
19+
"@vueuse/integrations": "^13.2.0",
2920
"globals": "^16.0.0",
3021
"laravel-vite-plugin": "^1.2.0",
3122
"lodash-es": "^4.17.21",
@@ -36,11 +27,23 @@
3627
"tailwindcss": "^4.0.17",
3728
"tailwindcss-primeui": "^0.6.1",
3829
"typescript": "^5.8.2",
30+
"universal-cookie": "^7.2.2",
3931
"unplugin-vue-components": "^28.4.1",
4032
"vite": "^6.2.3",
4133
"vue": "^3.5.13",
4234
"ziggy-js": "^2.5.2"
4335
},
36+
"devDependencies": {
37+
"@eslint/js": "^9.18.0",
38+
"@typescript-eslint/eslint-plugin": "^8.19.1",
39+
"@typescript-eslint/parser": "^8.19.1",
40+
"@vue/eslint-config-typescript": "^14.5.0",
41+
"eslint": "^9.18.0",
42+
"eslint-config-prettier": "^9.1.0",
43+
"eslint-plugin-vue": "^9.32.0",
44+
"typescript-eslint": "^8.19.1",
45+
"vue-tsc": "^2.2.8"
46+
},
4447
"optionalDependencies": {
4548
"@rollup/rollup-linux-x64-gnu": "4.37.0",
4649
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",

resources/css/app.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ body {
88
padding: 0 !important;
99
}
1010

11+
#app {
12+
visibility: hidden;
13+
}
14+
1115
#nprogress .bar {
1216
z-index: 9999999 !important;
1317
}

resources/js/app.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import '../css/app.css';
22
import '../css/tailwind.css';
33

4-
import { createApp, h } from 'vue';
4+
import { createSSRApp, h } from 'vue';
55
import { createInertiaApp, Head, Link } from '@inertiajs/vue3';
66
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
77
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
@@ -12,12 +12,9 @@ import ToastService from 'primevue/toastservice';
1212
import Container from '@/components/Container.vue';
1313
import PageTitleSection from '@/components/PageTitleSection.vue';
1414

15-
import { useColorMode } from '@vueuse/core';
15+
import { useSiteColorMode } from '@/composables/useSiteColorMode';
1616
import themePreset from '@/theme/noir-preset';
1717

18-
// Site light/dark mode
19-
const colorMode = useColorMode({ emitAuto: true });
20-
2118
/* global Ziggy */
2219
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
2320

@@ -29,8 +26,10 @@ createInertiaApp({
2926
import.meta.glob('./pages/**/*.vue')
3027
),
3128
setup({ el, App, props, plugin }) {
32-
return createApp({ render: () => h(App, props) })
33-
.provide('colorMode', colorMode)
29+
// Site light/dark mode
30+
const colorMode = useSiteColorMode({ emitAuto: true });
31+
32+
const app = createSSRApp({ render: () => h(App, props) })
3433
.use(plugin)
3534
.use(ZiggyVue, Ziggy)
3635
.use(PrimeVue, {
@@ -50,7 +49,14 @@ createInertiaApp({
5049
.component('InertiaLink', Link)
5150
.component('Container', Container)
5251
.component('PageTitleSection', PageTitleSection)
52+
.provide('colorMode', colorMode)
5353
.mount(el);
54+
55+
// #app content set to hidden by default
56+
// reduces jumpy initial render from SSR content (unstyled PrimeVue components)
57+
el.style.visibility = 'visible';
58+
59+
return app;
5460
},
5561
progress: {
5662
color: 'var(--p-primary-500)',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script setup>
2+
import { ref, onMounted } from 'vue';
3+
4+
const isMounted = ref(false);
5+
6+
onMounted(() => {
7+
isMounted.value = true;
8+
});
9+
</script>
10+
11+
<template>
12+
<slot v-if="isMounted" />
13+
</template>

resources/js/composables/useAppLayout.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ref, computed, onMounted, onUnmounted, watchEffect } from 'vue';
22
import { usePage, useForm } from '@inertiajs/vue3';
3-
import { route } from 'ziggy-js';
43
import { LayoutGrid, House, Info, Settings, LogOut, ExternalLink, FileSearch, FolderGit2 } from 'lucide-vue-next';
54
import { MenuItem } from '@/types';
65

@@ -74,21 +73,23 @@ export function useAppLayout() {
7473

7574
// Mobile menu
7675
const mobileMenuOpen = ref(false);
77-
const windowWidth = ref(window.innerWidth);
78-
const updateWidth = () => {
79-
windowWidth.value = window.innerWidth;
80-
};
81-
onMounted(() => {
82-
window.addEventListener('resize', updateWidth);
83-
});
84-
onUnmounted(() => {
85-
window.removeEventListener('resize', updateWidth);
86-
});
87-
watchEffect(() => {
88-
if (windowWidth.value > 1024) {
89-
mobileMenuOpen.value = false;
90-
}
91-
});
76+
if (typeof window !== 'undefined') {
77+
const windowWidth = ref(window.innerWidth);
78+
const updateWidth = () => {
79+
windowWidth.value = window.innerWidth;
80+
};
81+
onMounted(() => {
82+
window.addEventListener('resize', updateWidth);
83+
});
84+
onUnmounted(() => {
85+
window.removeEventListener('resize', updateWidth);
86+
});
87+
watchEffect(() => {
88+
if (windowWidth.value > 1024) {
89+
mobileMenuOpen.value = false;
90+
}
91+
});
92+
}
9293

9394
return {
9495
currentRoute,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useColorMode, type BasicColorSchema, type UseColorModeOptions } from '@vueuse/core';
2+
import { useCookies } from '@vueuse/integrations/useCookies';
3+
import type { CookieSetOptions } from 'universal-cookie';
4+
import { watch } from 'vue';
5+
6+
interface SiteColorModeOptions extends UseColorModeOptions {
7+
cookieKey?: string;
8+
cookieOpts?: CookieSetOptions;
9+
cookieColorMode?: BasicColorSchema;
10+
}
11+
12+
export function useSiteColorMode(opts: SiteColorModeOptions = {}) {
13+
const {
14+
cookieKey = 'colorScheme',
15+
cookieOpts: userOpts,
16+
cookieColorMode,
17+
...rest
18+
} = opts;
19+
20+
// a maxAge in seconds (365 days)
21+
const defaultOpts: CookieSetOptions = {
22+
path: '/',
23+
maxAge: 365 * 24 * 60 * 60,
24+
sameSite: 'lax',
25+
};
26+
27+
const finalCookieOpts = { ...defaultOpts, ...userOpts };
28+
29+
const cookies = useCookies([cookieKey]);
30+
const initialValue: BasicColorSchema = typeof window === 'undefined'
31+
? (cookieColorMode ?? 'auto')
32+
: (cookies.get(cookieKey) as BasicColorSchema) ?? 'auto';
33+
34+
const colorMode = useColorMode({ initialValue, ...rest });
35+
36+
if (typeof window !== 'undefined') {
37+
watch(colorMode, (mode) => {
38+
cookies.set(cookieKey, mode, finalCookieOpts);
39+
});
40+
}
41+
42+
return colorMode;
43+
}

resources/js/layouts/app/HeaderLayout.vue

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { usePage } from '@inertiajs/vue3';
44
import { useAppLayout } from '@/composables/useAppLayout';
55
import { ChevronsUpDown, ChevronDown, Menu as MenuIcon } from 'lucide-vue-next';
66
import ApplicationLogo from '@/components/ApplicationLogo.vue';
7+
import ClientOnly from '@/components/ClientOnly.vue';
78
import Menu from '@/components/primevue/menu/Menu.vue';
89
import Menubar from '@/components/primevue/menu/Menubar.vue';
910
import PanelMenu from '@/components/primevue/menu/PanelMenu.vue';
@@ -38,43 +39,45 @@ const toggleMobileUserMenu = (event) => {
3839

3940
<template>
4041
<div>
41-
<Teleport to="body">
42-
<!-- Mobile drawer menu -->
43-
<Drawer
44-
v-model:visible="mobileMenuOpen"
45-
position="right"
46-
>
47-
<div>
48-
<PanelMenu
49-
:model="menuItems"
50-
class="mt-1 w-full"
51-
/>
52-
</div>
53-
<template #footer>
54-
<div class="flex flex-col">
55-
<Button
56-
id="mobile-user-menu-btn"
57-
:label="page.props.auth.user.name"
58-
severity="secondary"
59-
size="large"
60-
pt:root:class="flex flex-row-reverse justify-between"
61-
@click="toggleMobileUserMenu($event)"
62-
>
63-
<template #icon>
64-
<ChevronsUpDown />
65-
</template>
66-
</Button>
67-
<Menu
68-
ref="mobile-user-menu"
69-
:model="userMenuItems"
70-
pt:root:class="z-[1200]"
71-
popup
42+
<ClientOnly>
43+
<Teleport to="body">
44+
<!-- Mobile drawer menu -->
45+
<Drawer
46+
v-model:visible="mobileMenuOpen"
47+
position="right"
48+
>
49+
<div>
50+
<PanelMenu
51+
:model="menuItems"
52+
class="mt-1 w-full"
7253
/>
7354
</div>
74-
</template>
75-
</Drawer>
76-
<Toast position="top-center" />
77-
</Teleport>
55+
<template #footer>
56+
<div class="flex flex-col">
57+
<Button
58+
id="mobile-user-menu-btn"
59+
:label="page.props.auth.user.name"
60+
severity="secondary"
61+
size="large"
62+
pt:root:class="flex flex-row-reverse justify-between"
63+
@click="toggleMobileUserMenu($event)"
64+
>
65+
<template #icon>
66+
<ChevronsUpDown />
67+
</template>
68+
</Button>
69+
<Menu
70+
ref="mobile-user-menu"
71+
:model="userMenuItems"
72+
pt:root:class="z-[1200]"
73+
popup
74+
/>
75+
</div>
76+
</template>
77+
</Drawer>
78+
<Toast position="top-center" />
79+
</Teleport>
80+
</ClientOnly>
7881
<div class="min-h-screen">
7982
<!-- Primary Navigation Menu -->
8083
<nav class="dynamic-bg shadow-sm">

0 commit comments

Comments
 (0)