Compare commits

..

4 Commits

Author SHA1 Message Date
07ec8f2ab1 feat: add canonical URL to metadata
All checks were successful
Build and Deploy Gabriel Kaszewski Portfolio / build-and-deploy-local (push) Successful in 1m9s
2026-05-16 14:05:42 +02:00
3301c6cfa5 feat: add gabrielkaszewski.pl to traefik router 2026-05-16 14:01:58 +02:00
71b871ed5c docs: add dual-domain implementation plan 2026-05-16 14:00:58 +02:00
f9f26dc584 docs: add dual-domain design spec 2026-05-16 13:59:48 +02:00
4 changed files with 155 additions and 1 deletions

View File

@@ -29,6 +29,9 @@ export const metadata: Metadata = {
"Next.js",
"Portfolio",
],
alternates: {
canonical: "https://gabrielkaszewski.dev",
},
openGraph: {
title: "Gabriel Kaszewski | Software Engineer",
description:

View File

@@ -5,7 +5,7 @@ services:
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.gabrielkaszewski.rule=Host(`gabrielkaszewski.dev`)"
- "traefik.http.routers.gabrielkaszewski.rule=Host(`gabrielkaszewski.dev`) || Host(`gabrielkaszewski.pl`)"
- "traefik.http.routers.gabrielkaszewski.entrypoints=websecure"
- "traefik.http.routers.gabrielkaszewski.tls.certresolver=letsencrypt"
networks:

View File

@@ -0,0 +1,113 @@
# Dual-Domain Support Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Serve the portfolio on both `gabrielkaszewski.dev` and `gabrielkaszewski.pl` with identical content, with `.dev` as the canonical domain for SEO.
**Architecture:** Two independent changes — Traefik routing config to accept both hostnames, and Next.js metadata to inject a canonical `<link>` tag. No app logic changes.
**Tech Stack:** Next.js 15 (App Router metadata API), Traefik (Docker labels), Let's Encrypt TLS.
---
### Task 1: Add `.pl` domain to Traefik router
**Files:**
- Modify: `compose.yml`
- [ ] **Step 1: Update the Traefik router rule**
In `compose.yml`, change the router rule label from:
```yaml
- "traefik.http.routers.gabrielkaszewski.rule=Host(`gabrielkaszewski.dev`)"
```
to:
```yaml
- "traefik.http.routers.gabrielkaszewski.rule=Host(`gabrielkaszewski.dev`) || Host(`gabrielkaszewski.pl`)"
```
The full `labels` block should look like:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.gabrielkaszewski.rule=Host(`gabrielkaszewski.dev`) || Host(`gabrielkaszewski.pl`)"
- "traefik.http.routers.gabrielkaszewski.entrypoints=websecure"
- "traefik.http.routers.gabrielkaszewski.tls.certresolver=letsencrypt"
```
Note: Traefik's Let's Encrypt cert resolver automatically provisions a cert for every hostname matched by the router rule, so no additional TLS labels are needed.
- [ ] **Step 2: Commit**
```bash
git add compose.yml
git commit -m "feat: add gabrielkaszewski.pl to traefik router"
```
---
### Task 2: Add canonical URL to Next.js metadata
**Files:**
- Modify: `app/layout.tsx`
- [ ] **Step 1: Add `alternates.canonical` to the metadata export**
In `app/layout.tsx`, add `alternates` to the existing `metadata` object:
```ts
export const metadata: Metadata = {
title: {
default: "Gabriel Kaszewski | Software Engineer",
template: "%s | Gabriel Kaszewski",
},
description:
"The portfolio of Gabriel Kaszewski, a software engineer specializing in Rust, Python, and modern web technologies.",
keywords: [
"Gabriel Kaszewski",
"Software Engineer",
"Rust Developer",
"Python Developer",
"Next.js",
"Portfolio",
],
alternates: {
canonical: "https://gabrielkaszewski.dev",
},
openGraph: {
// ... rest unchanged
},
// ... rest unchanged
};
```
- [ ] **Step 2: Verify the canonical tag is present in the built HTML**
Run the dev server:
```bash
bun run dev
```
Then in another terminal, check the rendered HTML for the canonical tag:
```bash
curl -s http://localhost:3000 | grep -i canonical
```
Expected output:
```
<link rel="canonical" href="https://gabrielkaszewski.dev"/>
```
- [ ] **Step 3: Commit**
```bash
git add app/layout.tsx
git commit -m "feat: add canonical URL to metadata"
```

View File

@@ -0,0 +1,38 @@
# Dual-Domain Support
**Date:** 2026-05-16
**Status:** Approved
## Goal
Serve the portfolio under both `gabrielkaszewski.dev` and `gabrielkaszewski.pl` with identical content. The `.dev` domain is canonical for SEO.
## Changes
### 1. `compose.yml` — Traefik router rule
Extend the `Host()` rule to accept both domains and cover both with TLS:
```yaml
- "traefik.http.routers.gabrielkaszewski.rule=Host(`gabrielkaszewski.dev`) || Host(`gabrielkaszewski.pl`)"
- "traefik.http.routers.gabrielkaszewski.entrypoints=websecure"
- "traefik.http.routers.gabrielkaszewski.tls.certresolver=letsencrypt"
```
### 2. `app/layout.tsx` — canonical metadata
Add `alternates.canonical` to the exported `metadata` object:
```ts
alternates: {
canonical: "https://gabrielkaszewski.dev",
},
```
This injects `<link rel="canonical" href="https://gabrielkaszewski.dev">` into every page's `<head>`, telling search engines the `.dev` domain is authoritative.
## Out of scope
- No redirects — `.pl` serves content directly.
- No per-domain content differences.
- No changes to OpenGraph or JSON-LD (already hardcoded to `.dev`).