Skip to content
Draft
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
18 changes: 18 additions & 0 deletions apps/dotnet/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
User-agent: *
Allow: /

# Sitemap
Sitemap: https://xprtz.net/sitemap-index.xml
Sitemap: https://xprtz.net/sitemap-0.xml

# Crawl delay (optional - adjust based on your server capacity)
Crawl-delay: 1

# Disallow admin or private areas (if any)
# Disallow: /admin/
# Disallow: /api/

# Allow important pages
Allow: /artikelen/
Allow: /images/
Allow: /fonts/
80 changes: 72 additions & 8 deletions apps/dotnet/src/layouts/layout.astro
Original file line number Diff line number Diff line change
@@ -1,16 +1,80 @@
---
import { BaseHead, Footer, Header } from '@xprtz/ui'
import Logo from '../images/logo_met_tekst.svg'
const { title, description } = Astro.props;
import {
BaseHead,
Footer,
Header,
type SEOData,
generateSEOData,
generateWebsiteStructuredData,
} from "@xprtz/ui";
import Logo from "../images/logo_met_tekst.svg";

interface Props {
title: string;
description: string;
image?: string;
imageAlt?: string;
type?: "website" | "article";
author?: string;
publishedTime?: string;
tags?: string[];
seoData?: Partial<SEOData>;
}

const {
title,
description,
image,
imageAlt,
type = "website",
author,
publishedTime,
tags,
seoData,
} = Astro.props;

// Generate base structured data for organization/website
const baseStructuredData = generateWebsiteStructuredData({
name: "XPRTZ",
url: Astro.site?.toString() || "https://xprtz.net",
description:
"Expert consultancy in .NET, Azure, and modern software development",
logo: `${Astro.site}${Logo.src}`,
sameAs: [
"https://www.linkedin.com/company/xprtz",
"https://github.com/xprtz",
"https://instagram.com/workwithxprtz",
"https://www.youtube.com/channel/UCFUV8Q5RgFMwN-XM_vkwT3g",
],
});

// Generate SEO data with all the props
const finalSEOData = generateSEOData({
title: title,
description: description,
image: image,
imageAlt: imageAlt,
type: type,
siteName: "XPRTZ",
structuredData: baseStructuredData,
additionalKeywords: tags,
...seoData,
});

// Override specific properties if provided
if (author) finalSEOData.author = author;
if (publishedTime) finalSEOData.publishedTime = publishedTime;
if (tags) finalSEOData.tags = tags;
---
<html lang="en">

<html lang="nl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<BaseHead title={title} description={description} />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<BaseHead {...finalSEOData} />
</head>
<body>
<Header Logo={Logo.src} client:load/>
<Header Logo={Logo.src} client:load />
<main class="isolate">
<slot />
</main>
Expand Down
50 changes: 46 additions & 4 deletions apps/dotnet/src/pages/artikelen/[article].astro
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
---
import Layout from "../../layouts/layout.astro";
import { fetchData, type Article } from "@xprtz/cms";
import { ContentAstro as Content } from "@xprtz/ui";

import {
ContentAstro as Content,
generateArticleSEOData,
generateArticleStructuredData,
} from "@xprtz/ui";

export async function getStaticPaths() {
const site = import.meta.env.PUBLIC_SITE || "no-site-found";
Expand All @@ -16,7 +19,7 @@ export async function getStaticPaths() {
"populate[authors][fields]": "*",
"populate[authors][populate][avatar][fields][0]=url": "url",
"populate[image][fields][0]": "url",
"populate[tags][fields][0]":"title",
"populate[tags][fields][0]": "title",
status: "published",
},
});
Expand All @@ -28,8 +31,47 @@ export async function getStaticPaths() {
}

const article: Article = Astro.props;

// Environment variables
const imagesUrl = import.meta.env.PUBLIC_IMAGES_URL || "";
const siteUrl = Astro.site?.toString() || "https://xprtz.net";

// Generate structured data for the article
const structuredData = generateArticleStructuredData({
article,
siteUrl,
siteName: "XPRTZ",
imagesUrl,
});

// Generate comprehensive SEO data
const seoData = generateArticleSEOData({
article,
imagesUrl,
siteName: "XPRTZ",
structuredData,
});

// Extract primary author info
const primaryAuthor = article.authors?.[0];
const authorName = primaryAuthor
? `${primaryAuthor.firstname} ${primaryAuthor.lastname}`
: undefined;

// Extract tag names for keywords
const tagNames = article.tags?.map((tag) => tag.title) || [];
---

<Layout title={article.title} description={article.title}>
<Layout
title={article.title}
description={article.title}
image={article.image ? `${imagesUrl}${article.image.url}` : undefined}
imageAlt={article.image?.alternateText || article.title}
type="article"
author={authorName}
publishedTime={article.date}
tags={tagNames}
seoData={seoData}
>
<Content article={article} />
</Layout>
73 changes: 66 additions & 7 deletions apps/dotnet/src/pages/artikelen/page/[page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import type { GetStaticPaths } from "astro";
import { fetchData, type Article, type Page as PageType } from "@xprtz/cms";
import Layout from "../../../layouts/layout.astro";
import { Container, Blogs } from "@xprtz/ui";
import {
Container,
Blogs,
SEOTools,
generateWebsiteStructuredData,
} from "@xprtz/ui";

interface PageData {
data: Article[];
Expand Down Expand Up @@ -44,6 +49,8 @@ export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
};

const site = import.meta.env.PUBLIC_SITE || "no-site-found";
const siteUrl = Astro.site?.toString() || "https://xprtz.net";

const articlesPages = await fetchData<Array<PageType>>({
endpoint: "pages",
wrappedByKey: "data",
Expand All @@ -55,14 +62,66 @@ const articlesPages = await fetchData<Array<PageType>>({
});

const { page } = Astro.props as { page: PageData };

const articlesPage = articlesPages[0];

// Generate pagination URLs
const prevPageUrl = page.url.prev ? `${siteUrl}${page.url.prev}` : undefined;
const nextPageUrl = page.url.next ? `${siteUrl}${page.url.next}` : undefined;

// Generate structured data for the blog listing page
const structuredData = generateWebsiteStructuredData({
name: `${articlesPage.title_website} - Pagina ${page.currentPage}`,
url: `${siteUrl}${page.url.current}`,
description: `${articlesPage.description} - Pagina ${page.currentPage} van ${page.lastPage}`,
});

// Generate breadcrumbs
const breadcrumbs = [
{ name: "Home", url: siteUrl },
{ name: articlesPage.title_website, url: `${siteUrl}/artikelen/` },
...(Number(page.currentPage) > 1
? [
{
name: `Pagina ${page.currentPage}`,
url: `${siteUrl}${page.url.current}`,
},
]
: []),
];

// Create page-specific title and description
const pageTitle =
Number(page.currentPage) > 1
? `${articlesPage.title_website} - Pagina ${page.currentPage}`
: articlesPage.title_website;

const pageDescription =
Number(page.currentPage) > 1
? `${articlesPage.description} - Pagina ${page.currentPage} van ${page.lastPage}`
: articlesPage.description;
---

<Layout
title={articlesPage.title_website}
description={articlesPage.description}
title={pageTitle}
description={pageDescription}
type="website"
seoData={{
structuredData,
keywords: [
"articles",
"blog posts",
".NET development",
"software engineering",
"programming tutorials",
],
}}
>
<SEOTools
prevPage={prevPageUrl}
nextPage={nextPageUrl}
breadcrumbs={breadcrumbs}
/>

<Container>
<svg
class="absolute inset-x-0 top-0 -z-10 h-[64rem] w-full stroke-gray-200 [mask-image:radial-gradient(32rem_32rem_at_center,white,transparent)]"
Expand Down Expand Up @@ -113,11 +172,11 @@ const articlesPage = articlesPages[0];
<h1
class="mt-2 text-pretty text-4xl font-semibold tracking-tight text-primary-800 sm:text-5xl"
>
{articlesPage.title_website}
{pageTitle}
</h1>
<p class="mt-6 text-xl/8">{articlesPage.description}</p>
<p class="mt-6 text-xl/8">{pageDescription}</p>
<div class="mt-10 max-w-7xl text-base/7">
<Blogs page={page}/>
<Blogs page={page} />
</div>
</div>
</div>
Expand Down
56 changes: 54 additions & 2 deletions apps/dotnet/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
---
import { fetchData, type HomePage } from "@xprtz/cms";
import { ComponentRenderer } from "@xprtz/ui";
import {
ComponentRenderer,
generateWebsiteStructuredData,
generateOrganizationStructuredData,
} from "@xprtz/ui";

import Layout from "../layouts/layout.astro";

const site = import.meta.env.PUBLIC_SITE || "no-site-found";
const siteUrl = Astro.site?.toString() || "https://xprtz.net";

const homepages = await fetchData<Array<HomePage>>({
endpoint: "homepages",
Expand All @@ -19,7 +24,54 @@ const homepage = homepages[0];

const title = homepage.title;
const description = homepage.description;

// Generate structured data for the homepage
const websiteStructuredData = generateWebsiteStructuredData({
name: "XPRTZ",
url: siteUrl,
description: description,
sameAs: [
"https://www.linkedin.com/company/xprtz",
"https://github.com/xprtz",
"https://instagram.com/workwithxprtz",
"https://www.youtube.com/channel/UCFUV8Q5RgFMwN-XM_vkwT3g",
],
});

// Generate Organization structured data for rich results
const organizationStructuredData = generateOrganizationStructuredData({
name: "XPRTZ",
url: siteUrl,
logo: `${siteUrl}/images/logo.svg`,
description:
"XPRTZ is a leading .NET and Azure consulting company specializing in enterprise software development, cloud solutions, and Microsoft technology stack implementations.",
sameAs: [
"https://www.linkedin.com/company/xprtz",
"https://github.com/xprtz",
"https://instagram.com/workwithxprtz",
"https://www.youtube.com/channel/UCFUV8Q5RgFMwN-XM_vkwT3g",
],
});

// Add keywords related to your business
const keywords = [
".NET development",
"Azure consulting",
"Software development",
"Cloud solutions",
"Microsoft technology",
"Enterprise applications",
];
---
<Layout title={title} description={description}>

<Layout
title={title}
description={description}
type="website"
seoData={{
structuredData: [websiteStructuredData, organizationStructuredData],
keywords,
}}
>
<ComponentRenderer components={homepage.components} />
</Layout>
Loading