Skip to content
Merged

SEO #42

Show file tree
Hide file tree
Changes from 2 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
Binary file added public/images/pages/conduct.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/current-speakers.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/home.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/parners.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/past-speakers.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/privacy.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/schedule.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/sponsors.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/pages/team.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/app/conduct/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import { createMetadata } from "@/lib/seo";

export const metadata = createMetadata({
title: "Code of Conduct",
description:
"SINFO is dedicated to providing a harassment-free conference experience for everyone. Read our Code of Conduct.",
path: "/conduct",
image: "/images/pages/conduct.jpg",
});

export default function CodeConduct() {
return (
<main className="min-h-screen bg-gray-100">
Expand Down
60 changes: 58 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,65 @@ import { EventService } from "@/services/EventService";

const montserrat = Montserrat({ subsets: ["latin"] });

const siteUrl = "https://sinfo.org";

export const metadata: Metadata = {
title: "SINFO Website",
description: "SINFO Website",
metadataBase: new URL(siteUrl),
title: {
default: "SINFO — Portugal's Biggest Free Tech Conference",
template: "%s | SINFO",
},
description:
"SINFO is Portugal's biggest free technology conference, held annually at Instituto Superior Técnico in Lisbon. Join thousands of tech enthusiasts, industry leaders, and innovators.",
keywords: [
"SINFO",
"tech conference",
"Portugal",
"Lisbon",
"IST",
"technology",
"speakers",
"innovation",
"free conference",
],
authors: [{ name: "SINFO", url: siteUrl }],
creator: "SINFO",
openGraph: {
type: "website",
locale: "en_US",
url: siteUrl,
siteName: "SINFO",
title: "SINFO — Portugal's Biggest Free Tech Conference",
description:
"SINFO is Portugal's biggest free technology conference, held annually at Instituto Superior Técnico in Lisbon. Join thousands of tech enthusiasts, industry leaders, and innovators.",
images: [
{
url: "/images/pages/home.jpg",
alt: "SINFO — Portugal's Biggest Free Tech Conference",
},
],
},
twitter: {
card: "summary_large_image",
title: "SINFO — Portugal's Biggest Free Tech Conference",
description:
"SINFO is Portugal's biggest free technology conference, held annually at Instituto Superior Técnico in Lisbon.",
images: ["/images/pages/home.jpg"],
creator: "@sinfosl",
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-image-preview": "large",
"max-snippet": -1,
},
},
alternates: {
canonical: siteUrl,
},
};

export const viewport: Viewport = {
Expand Down
5 changes: 3 additions & 2 deletions src/app/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import type { MetadataRoute } from "next";

export default function manifest(): MetadataRoute.Manifest {
return {
name: "SINFO - Website",
name: "SINFO — Portugal's Biggest Free Tech Conference",
short_name: "SINFO",
description: "SINFO Website",
description:
"SINFO is Portugal's biggest free technology conference, held annually at Instituto Superior Técnico in Lisbon.",
start_url: "/",
display: "standalone",
background_color: "#1c2b70", // SINFO Primary
Expand Down
9 changes: 9 additions & 0 deletions src/app/partners/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createMetadata } from "@/lib/seo";
import { CompanyService } from "@/services/CompanyService";

export const metadata = createMetadata({
title: "Partners",
description:
"Meet the organisations and partners that collaborate with SINFO to deliver an unforgettable tech conference experience.",
path: "/partners",
image: "/images/pages/parners.jpg",
});
import { EventService } from "@/services/EventService";
import BlankPageMessage from "@/components/BlankPageMessage";
import GridList from "@/components/GridList";
Expand Down
10 changes: 10 additions & 0 deletions src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import { createMetadata } from "@/lib/seo";

export const metadata = createMetadata({
title: "Privacy Policy",
description:
"Read SINFO's Privacy Policy to understand how we collect, use, and protect your personal information.",
path: "/privacy",
image: "/images/pages/privacy.jpg",
});

export default function PrivacyPage() {
return (
<main className="min-h-screen bg-gray-100">
Expand Down
9 changes: 9 additions & 0 deletions src/app/schedule/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createMetadata } from "@/lib/seo";
import Link from "next/link";

export const metadata = createMetadata({
title: "Schedule",
description:
"Check out the SINFO event schedule — talks, workshops, panels, and more.",
path: "/schedule",
image: "/images/pages/schedule.jpg",
});
import ImageWithFallback from "@/components/ImageWithFallback";
import { workHacky } from "@/assets/images";
import CallToAction from "@/components/CallToAction";
Expand Down
24 changes: 24 additions & 0 deletions src/app/speakers/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from "react";
import type { Metadata } from "next";
import Link from "next/link";
import { SpeakerService } from "@/services/SpeakerService";
import { SessionService } from "@/services/SessionService";
import { generateTimeInterval } from "@/utils/utils";
import ImageWithFallback from "@/components/ImageWithFallback";
import { ShowMore } from "@/components/ShowMore";
import { Calendar, Clock, MapPin } from "lucide-react";
import { createMetadata } from "@/lib/seo";

export const dynamic = "force-dynamic";

Expand All @@ -15,6 +17,28 @@ type Props = {
};
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const speaker = await SpeakerService.getSpeaker(params.id);
if (!speaker) return { title: "Speaker Not Found" };

const description = speaker.description
? speaker.description.slice(0, 160)
: `${speaker.name} — speaker at SINFO, Portugal's biggest free tech conference.`;

const seoImageUrl = `https://static.sinfo.org/website/33-sinfo/seo/speakers/${params.id}.jpg`;
const seoImageExists = await fetch(seoImageUrl, { method: "HEAD" })
.then((r) => r.ok)
.catch(() => false);
const image = seoImageExists ? seoImageUrl : "/images/pages/home.jpg";

return createMetadata({
title: speaker.name,
description,
path: `/speakers/${params.id}`,
image,
});
}

export default async function Page({ params }: Props) {
const { id } = params;
const speaker = await SpeakerService.getSpeaker(id);
Expand Down
9 changes: 9 additions & 0 deletions src/app/speakers/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import React from "react";
import { createMetadata } from "@/lib/seo";
import BlankPageMessage from "@/components/BlankPageMessage";

export const metadata = createMetadata({
title: "Speakers",
description:
"Meet the world-class speakers at SINFO — influential minds in technology and innovation shaping the future.",
path: "/speakers",
image: "/images/pages/current-speakers.jpg",
});
import SpeakerCard from "@/components/Home/CurrentSpeakersHighlight/SpeakerCard";
import { SpeakerService } from "@/services/SpeakerService";
import { EventService } from "@/services/EventService";
Expand Down
9 changes: 9 additions & 0 deletions src/app/speakers/previous/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import React from "react";
import { createMetadata } from "@/lib/seo";
import BlankPageMessage from "@/components/BlankPageMessage";

export const metadata = createMetadata({
title: "Past Speakers",
description:
"Explore the past speakers of SINFO — global voices that helped shape Portugal's biggest free tech conference.",
path: "/speakers/previous",
image: "/images/pages/past-speakers.jpg",
});
import SpeakerCard from "@/components/Home/CurrentSpeakersHighlight/SpeakerCard";
import { SpeakerService } from "@/services/SpeakerService";
import { buildEditionColorMap } from "@/utils/speakerColors";
Expand Down
9 changes: 9 additions & 0 deletions src/app/sponsors/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createMetadata } from "@/lib/seo";
import { CompanyService } from "@/services/CompanyService";

export const metadata = createMetadata({
title: "Sponsors",
description:
"Meet the amazing sponsors that make SINFO possible — companies committed to delivering a free world-class tech conference.",
path: "/sponsors",
image: "/images/pages/sponsors.jpg",
});
import { EventService } from "@/services/EventService";
import BlankPageMessage from "@/components/BlankPageMessage";
import GridList from "@/components/GridList";
Expand Down
9 changes: 9 additions & 0 deletions src/app/team/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createMetadata } from "@/lib/seo";
import { MemberService } from "@/services/MemberService";

export const metadata = createMetadata({
title: "Team",
description:
"Meet the passionate students behind SINFO — the team that organises Portugal's biggest free tech conference.",
path: "/team",
image: "/images/pages/team.jpg",
});
import { EventService } from "@/services/EventService";
import MemberCard from "@/components/MemberCard";
import BlankPageMessage from "@/components/BlankPageMessage";
Expand Down
18 changes: 9 additions & 9 deletions src/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export async function register() {
if (
process.env.NEXT_RUNTIME == "nodejs" &&
process.env.NODE_ENV == "development"
) {
const { server } = await import("@/mocks/node");
server.listen({
onUnhandledRequest: "bypass",
});
}
// if (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this file

// process.env.NEXT_RUNTIME == "nodejs" &&
// process.env.NODE_ENV == "development"
// ) {
// const { server } = await import("@/mocks/node");
// server.listen({
// onUnhandledRequest: "bypass",
// });
// }
}
48 changes: 48 additions & 0 deletions src/lib/seo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Metadata } from "next";
import { headers } from "next/headers";

const defaultImage = "/images/pages/home.jpg";

function getSiteUrl(): string {
try {
const host = headers().get("host") ?? "sinfo.org";
const proto = host.startsWith("localhost") ? "http" : "https";
return `${proto}://${host}`;
} catch {
return "https://sinfo.org";
}
}

export function createMetadata({
title,
description,
path = "",
image = defaultImage,
}: {
title: string;
description: string;
path?: string;
image?: string;
}): Metadata {
const siteUrl = getSiteUrl();
const url = `${siteUrl}${path}`;
const absoluteImage = image.startsWith("http") ? image : `${siteUrl}${image}`;

return {
title,
description,
openGraph: {
title: `${title} | SINFO`,
description,
url,
images: [{ url: absoluteImage, alt: title }],
},
twitter: {
card: "summary_large_image",
title: `${title} | SINFO`,
description,
images: [absoluteImage],
},
alternates: { canonical: url },
};
}
Loading