Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion caddy/WebCaddyfile.internal
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# Static SPA
handle {
root * /usr/share/caddy
try_files {path} /index.html
try_files {path} {path}/index.html /index.html
file_server
}

Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<meta name="twitter:image" content="/favicon.ico" />

<!-- Structured Data for AI and crawlers -->
<script type="application/ld+json">
<script id="seo-jsonld" type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
Expand Down
Binary file added public/images/profile/about.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions src/pages/AboutPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@

<script setup lang="ts">
import { computed, ref, onMounted } from 'vue';
import { useSeo, SITE_NAME } from '@/support/seo';
import AboutPicture from '@images/profile/about.jpg';
import FooterPartial from '@partials/FooterPartial.vue';
import HeaderPartial from '@partials/HeaderPartial.vue';
import SideNavPartial from '@partials/SideNavPartial.vue';
import WidgetSocialPartial from '@partials/WidgetSocialPartial.vue';
import WidgetSkillsPartial from '@partials/WidgetSkillsPartial.vue';
import { useSeo, SITE_NAME, ABOUT_IMAGE, siteUrlFor } from '@/support/seo';

import { useApiStore } from '@api/store.ts';
import { debugError } from '@api/http-error.ts';
Expand All @@ -109,8 +109,13 @@ const formattedNickname = computed((): string => {

useSeo({
title: 'About',
image: ABOUT_IMAGE,
url: siteUrlFor('/about'),
description: `${SITE_NAME} is an engineering leader who’s passionate about building reliable and smooth software.`,
image: AboutPicture,
jsonLd: {
'@type': 'AboutPage',
name: 'About',
},
});

onMounted(async () => {
Expand Down
10 changes: 7 additions & 3 deletions src/pages/HomePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,20 @@ import { onMounted, ref } from 'vue';
import { useApiStore } from '@api/store.ts';
import { debugError } from '@api/http-error.ts';
import type { ProfileResponse } from '@api/response/index.ts';
import { useSeo, SITE_NAME } from '@/support/seo';
import ogImage from '@images/profile/about.jpg';
import { useSeo, SITE_NAME, ABOUT_IMAGE, siteUrlFor } from '@/support/seo';

const apiStore = useApiStore();
const profile = ref<ProfileResponse | null>(null);

useSeo({
title: 'Home',
image: ABOUT_IMAGE,
url: siteUrlFor('/'),
description: `${SITE_NAME} is a full-stack Software Engineer leader & architect with over two decades of experience in building complex web systems and products.`,
image: ogImage,
jsonLd: {
'@type': 'WebPage',
name: 'Home',
},
});

onMounted(async () => {
Expand Down
6 changes: 2 additions & 4 deletions src/pages/PostPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
<script setup lang="ts">
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import { useSeoFromPost } from '@/support/seo';
import { siteUrlFor, useSeoFromPost } from '@/support/seo';
import { useRoute } from 'vue-router';
import { useApiStore } from '@api/store.ts';
import { useDarkMode } from '@/dark-mode.ts';
Expand Down Expand Up @@ -155,9 +155,7 @@ const xURLFor = (post: PostResponse) => {
return `https://x.com/intent/tweet?url=${fullURLFor(post)}&text=${post.title}`;
};

const fullURLFor = (post: PostResponse) => {
return `${window.location.origin}/posts/${post.slug}`;
};
const fullURLFor = (post: PostResponse) => siteUrlFor(`/post/${post.slug}`);

async function sharePost(post: PostResponse) {
const shareData = {
Expand Down
10 changes: 7 additions & 3 deletions src/pages/ProjectsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,14 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useApiStore } from '@api/store.ts';
import { useSeo, SITE_NAME } from '@/support/seo';
import ogImage from '@images/profile/about.jpg';
import { debugError } from '@api/http-error.ts';
import FooterPartial from '@partials/FooterPartial.vue';
import HeaderPartial from '@partials/HeaderPartial.vue';
import SideNavPartial from '@partials/SideNavPartial.vue';
import ProjectCardPartial from '@partials/ProjectCardPartial.vue';
import WidgetSkillsPartial from '@partials/WidgetSkillsPartial.vue';
import WidgetSponsorPartial from '@partials/WidgetSponsorPartial.vue';
import { useSeo, SITE_NAME, ABOUT_IMAGE, siteUrlFor } from '@/support/seo';
import type { ProfileResponse, ProjectsResponse } from '@api/response/index.ts';

const apiStore = useApiStore();
Expand All @@ -75,8 +74,13 @@ const profile = ref<ProfileResponse | null>(null);

useSeo({
title: 'Projects',
image: ABOUT_IMAGE,
url: siteUrlFor('/projects'),
description: `Explore some of ${SITE_NAME} open source and client projects built to solve real engineering challenges.`,
image: ogImage,
jsonLd: {
'@type': 'CollectionPage',
name: 'Projects',
},
});

onMounted(async () => {
Expand Down
10 changes: 7 additions & 3 deletions src/pages/ResumePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ import RecommendationPartial from '@partials/RecommendationPartial.vue';

import { ref, onMounted } from 'vue';
import { useApiStore } from '@api/store.ts';
import { useSeo, SITE_NAME } from '@/support/seo';
import ogImage from '@images/profile/about.jpg';
import { debugError } from '@api/http-error.ts';
import { useSeo, SITE_NAME, ABOUT_IMAGE, siteUrlFor } from '@/support/seo';
import type { ProfileResponse, EducationResponse, ExperienceResponse, RecommendationsResponse } from '@api/response/index.ts';

const apiStore = useApiStore();
Expand All @@ -67,8 +66,13 @@ const recommendations = ref<RecommendationsResponse[] | null>(null);

useSeo({
title: 'Resume',
image: ABOUT_IMAGE,
url: siteUrlFor('/resume'),
description: `Explore the experience, education, and recommendations of ${SITE_NAME}.`,
image: ogImage,
jsonLd: {
'@type': 'ProfilePage',
name: 'Resume',
},
});

onMounted(async () => {
Expand Down
10 changes: 7 additions & 3 deletions src/pages/SubscribePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,20 @@
</template>

<script setup lang="ts">
import { useSeo, SITE_NAME } from '@/support/seo';
import ogImage from '@images/profile/about.jpg';
import HeaderPartial from '@partials/HeaderPartial.vue';
import FooterPartial from '@partials/FooterPartial.vue';
import SideNavPartial from '@partials/SideNavPartial.vue';
import WidgetSponsorPartial from '@partials/WidgetSponsorPartial.vue';
import { useSeo, SITE_NAME, siteUrlFor, ABOUT_IMAGE } from '@/support/seo';

useSeo({
title: 'Subscribe',
image: ABOUT_IMAGE,
url: siteUrlFor('/subscribe'),
description: `Subscribe to ${SITE_NAME}'s newsletter to updates of articles and cool things he is working on.`,
image: ogImage,
jsonLd: {
'@type': 'WebPage',
name: 'Subscribe',
},
});
</script>
11 changes: 8 additions & 3 deletions src/support/seo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { computed, onBeforeUnmount, unref, watchEffect, type MaybeRefOrGetter } from 'vue';
import type { PostResponse } from '@api/response/posts-response.ts';

export const DEFAULT_SITE_URL = 'https://oullin.io';
export const SITE_NAME = 'Gustavo Ocanto';
export const DEFAULT_SITE_URL = 'https://oullin.io';
export const ABOUT_IMAGE = '/images/profile/about.jpg';
export const SITE_URL = (import.meta.env?.VITE_SITE_URL as string | undefined) ?? (typeof window !== 'undefined' ? window.location.origin : DEFAULT_SITE_URL);

type TwitterCard = 'summary' | 'summary_large_image' | 'app' | 'player';
Expand Down Expand Up @@ -42,7 +43,7 @@ export class Seo {
if (!hasDocument || !hasWindow) return;

const currentPath = window.location.pathname + window.location.search;
const url = options.url ?? new URL(currentPath, SITE_URL).toString();
const url = options.url ?? siteUrlFor(currentPath || '/');
const image = options.image ? new URL(options.image, SITE_URL).toString() : undefined;
const title = options.title ? `${options.title} - ${SITE_NAME}` : SITE_NAME;
const description = options.description;
Expand Down Expand Up @@ -191,7 +192,7 @@ export function useSeoFromPost(post: MaybeRefOrGetter<PostResponse | null | unde
description: value.excerpt,
image: value.cover_image_url,
type: 'article',
url: new URL(`/posts/${value.slug}`, SITE_URL).toString(),
url: siteUrlFor(`/post/${value.slug}`),
jsonLd: {
'@context': 'https://schema.org',
'@type': 'Article',
Expand All @@ -209,3 +210,7 @@ export function useSeoFromPost(post: MaybeRefOrGetter<PostResponse | null | unde

useSeo(seoOptions);
}

export function siteUrlFor(path: string): string {
return new URL(path, SITE_URL).toString();
}