feat: prerender public site pages
This commit is contained in:
59
frontend/src/entry-public-ssr.js
Normal file
59
frontend/src/entry-public-ssr.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createSSRApp, h } from 'vue';
|
||||
import { createMemoryHistory, createRouter, RouterView } from 'vue-router';
|
||||
import { createHead, renderHeadToString } from '@vueuse/head';
|
||||
import { renderToString } from '@vue/server-renderer';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import en from '@/locales/en.json';
|
||||
import fr from '@/locales/fr.json';
|
||||
import Landing from '@/features/landing/views/Landing.vue';
|
||||
import ProductPage from '@/features/landing/views/ProductPage.vue';
|
||||
import PricingPage from '@/features/landing/views/PricingPage.vue';
|
||||
import BlogsPage from '@/features/landing/views/BlogsPage.vue';
|
||||
import GuidesPage from '@/features/landing/views/GuidesPage.vue';
|
||||
import './assets/main.css';
|
||||
|
||||
const publicRoutes = [
|
||||
{ path: '/', component: Landing },
|
||||
{ path: '/product', component: ProductPage },
|
||||
{ path: '/pricing', component: PricingPage },
|
||||
{ path: '/blogs', component: BlogsPage },
|
||||
{ path: '/guides', component: GuidesPage },
|
||||
{ path: '/login', component: { render: () => null } },
|
||||
{ path: '/register', component: { render: () => null } },
|
||||
];
|
||||
|
||||
export async function render(routePath) {
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: publicRoutes,
|
||||
});
|
||||
|
||||
const head = createHead();
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en,
|
||||
fr,
|
||||
},
|
||||
});
|
||||
|
||||
const app = createSSRApp({
|
||||
render: () => h(RouterView),
|
||||
});
|
||||
|
||||
app.use(router);
|
||||
app.use(head);
|
||||
app.use(i18n);
|
||||
|
||||
await router.push(routePath);
|
||||
await router.isReady();
|
||||
|
||||
const appHtml = await renderToString(app);
|
||||
const headTags = await renderHeadToString(head);
|
||||
|
||||
return {
|
||||
appHtml,
|
||||
headTags,
|
||||
};
|
||||
}
|
||||
51
frontend/src/features/landing/publicPageMeta.js
Normal file
51
frontend/src/features/landing/publicPageMeta.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useHead } from '@vueuse/head';
|
||||
|
||||
function getCanonicalUrl(path) {
|
||||
const configuredSiteUrl = import.meta.env.VITE_PUBLIC_SITE_URL
|
||||
?? (typeof process !== 'undefined' ? process.env.VITE_PUBLIC_SITE_URL : undefined)
|
||||
?? (typeof process !== 'undefined' ? process.env.SITE_URL : undefined);
|
||||
|
||||
if (configuredSiteUrl) {
|
||||
return new URL(path, `${configuredSiteUrl.replace(/\/$/, '')}/`).toString();
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
return path;
|
||||
}
|
||||
|
||||
return new URL(path, window.location.origin).toString();
|
||||
}
|
||||
|
||||
export function usePublicPageMeta({ title, description, path }) {
|
||||
useHead({
|
||||
title,
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: description,
|
||||
},
|
||||
{
|
||||
name: 'robots',
|
||||
content: 'index,follow',
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: title,
|
||||
},
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description,
|
||||
},
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
},
|
||||
],
|
||||
link: [
|
||||
{
|
||||
rel: 'canonical',
|
||||
href: getCanonicalUrl(path),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Blogs | Socialize',
|
||||
description: 'Practical articles on content review workflows, client approval, revision tracking, and publication handoff.',
|
||||
path: '/blogs',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Guides | Socialize',
|
||||
description: 'Reusable guides for content intake, review rounds, approval decisions, and delivery readiness.',
|
||||
path: '/guides',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Socialize | Social media approval workflow',
|
||||
description: 'Socialize helps teams manage social media content review, revisions, approval decisions, and publication handoff in one workflow.',
|
||||
path: '/',
|
||||
});
|
||||
|
||||
const pillars = computed(() => [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Pricing | Socialize',
|
||||
description: 'Socialize workspace pricing for teams managing social media content approvals with clients.',
|
||||
path: '/pricing',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Product | Socialize',
|
||||
description: 'Socialize keeps content items, assets, revisions, comments, approval decisions, and publishing handoff details in one workspace.',
|
||||
path: '/product',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -34,6 +34,7 @@ import { useNotificationsStore } from '@/features/notifications/stores/notificat
|
||||
import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
|
||||
import { i18n } from '@/plugins/i18n.js';
|
||||
import config from '@/config.js';
|
||||
import { createHead } from '@vueuse/head';
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components: {
|
||||
@@ -78,9 +79,11 @@ const vuetify = createVuetify({
|
||||
});
|
||||
|
||||
const pinia = createPinia();
|
||||
const head = createHead();
|
||||
|
||||
const app = createApp(App)
|
||||
.use(pinia)
|
||||
.use(head)
|
||||
.use(vuetify)
|
||||
.use(router)
|
||||
.use(i18n)
|
||||
|
||||
Reference in New Issue
Block a user