Compare commits
4 Commits
97e53d24f0
...
ac9ab52d63
| Author | SHA1 | Date | |
|---|---|---|---|
| ac9ab52d63 | |||
| 4f16fce5fe | |||
| d5fb608091 | |||
| e6e17d880e |
@@ -3,7 +3,7 @@ const currentPath = Astro.url.pathname;
|
|||||||
import Logo from "../assets/logo.svg";
|
import Logo from "../assets/logo.svg";
|
||||||
---
|
---
|
||||||
|
|
||||||
<nav class="main-nav glass-container">
|
<nav class="main-nav glass-container" style="view-transition-name:navigation;">
|
||||||
<div class="nav-content">
|
<div class="nav-content">
|
||||||
<a href="/" class="logo">
|
<a href="/" class="logo">
|
||||||
<Logo class="logo-img" />
|
<Logo class="logo-img" />
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const portfolioCollection = defineCollection({
|
|||||||
summary: z.string(),
|
summary: z.string(),
|
||||||
tags: z.array(z.string()).optional(),
|
tags: z.array(z.string()).optional(),
|
||||||
pubDate: z.date(),
|
pubDate: z.date(),
|
||||||
|
repository: z.url().regex(/git/).optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,65 @@
|
|||||||
title: Astro Rewrite
|
title: Astro Rewrite
|
||||||
summary: "Next.js entwickelt sich rasend - zu schnell für mich, um Features und Sicherheitsupdates immer zu verfolgen. Und React für einen Blog? Unnötig. Eine einfache statische Lösung aber mit Komponenten? Astro ist genau das. Also: Willkommen zum neuen Anstrich in Astro!"
|
summary: "Next.js entwickelt sich rasend - zu schnell für mich, um Features und Sicherheitsupdates immer zu verfolgen. Und React für einen Blog? Unnötig. Eine einfache statische Lösung aber mit Komponenten? Astro ist genau das. Also: Willkommen zum neuen Anstrich in Astro!"
|
||||||
repository: "https://git.c0ntroller.de/c0ntroller/frontpage"
|
repository: "https://git.c0ntroller.de/c0ntroller/frontpage"
|
||||||
pubDate: 2023-04-03
|
pubDate: 2025-03-22
|
||||||
tags:
|
tags:
|
||||||
- Astro
|
- Astro
|
||||||
- SSR
|
|
||||||
- Webentwicklung
|
- Webentwicklung
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Next.js war einfach Magie.
|
||||||
|
Als ich React lernte und noch überlegte, wie ich am besten ein Backend dazu stelle, fragete ein Freund, warum ich nicht einfach Next.js nehme.
|
||||||
|
Und das war magisch.
|
||||||
|
Denn ich konnte mein Gelerntes nutzen und hatte trotzdem direkt ein Backend mit dabei.
|
||||||
|
Ich hab mir darin nicht nur ein kleines Projekt gebaut, sondern später auch meine erste Website.
|
||||||
|
|
||||||
|
Die Struktur war einfach: Eine Datei in `app` anlegen und... Moment.
|
||||||
|
Stimmt gar nicht mehr.
|
||||||
|
Also: Eine Datei in `pages` anlegen. Oder doch in `app`?
|
||||||
|
Beides Ok.
|
||||||
|
Achso, außer dass `app` dann deprecated wurde.
|
||||||
|
|
||||||
|
Next.js war für mich die erste Berührung mit dem JavaScript Ökosystem und plötzlich verstand ich die ganzen Memes.
|
||||||
|
Dann kam auch plötzlich die Nacht vom dependebot und ich hatte plötzlich 20 PRs, die alle Next.js Updates waren.
|
||||||
|
Nur, dass ab irgendeinem Punkt man plötzlich sich mit komplizierteren Updates rumschlagen musste und nicht einfach nur `npm audit fix` machen konnte.
|
||||||
|
|
||||||
|
Also brauchte es eine Alternative.
|
||||||
|
|
||||||
|
## Sinneswandel
|
||||||
|
|
||||||
|
React ist cool.
|
||||||
|
Ziemlich performant für das, was es macht, sehr flexibel und einmal gelernt kann man auch andere Frameworks leicht lernen.
|
||||||
|
|
||||||
|
Aber wofür braucht eine Homepage eine komplexe reaktive Komponente?
|
||||||
|
Naja, eigentlich braucht sie das nicht.
|
||||||
|
Aber es war an dem Punkt für mich die einzige Möglichkeit absiets von Vanilla HTML5, CSS und JS zu arbeiten.
|
||||||
|
|
||||||
|
Gleichzeitig merkte ich bei immer mehr Seiten, dass hier Unmengen an Rechenleistung in JavaScript nur für hübsche Effekte drauf ging.
|
||||||
|
Ich bin sehr für schöne Effekte, aber immer mehr fand ich, dass man als **guter** Web Entwickler eigentlich so wenig JavaScript wie möglich einsetzen sollte.
|
||||||
|
JavaScript Frameworks sind im Normalfall aber das genaue Gegenteil.
|
||||||
|
|
||||||
|
Ich wollte meine Seite also neu machen und hatte eien Vision, die nicht ganz erreichbar erschien.
|
||||||
|
Und dann fand ich ein Framework, dass eigentlich genau das war, was ich suchte: Astro.
|
||||||
|
|
||||||
|
## Astro
|
||||||
|
|
||||||
|
Astro zeichnet sich dadurch aus, dass es komplett statische Seiten generiert.
|
||||||
|
Falls man das braucht, kann man aber auch Server Side Rendering nutzen, oder sogar beides gleichzeitig.
|
||||||
|
Islands gibt es sogar für die verschiedensten Frameworks.
|
||||||
|
|
||||||
|
Aber all das brauchte ich gar nicht.
|
||||||
|
Eine cleane modulare Webseite aufbauen, mit verschachtelten Komponenten, scoped CSS und ohne JavaScript, dass man nicht selbst schreibt.
|
||||||
|
Das war genau das, was ich suchte.
|
||||||
|
|
||||||
|
Und so habe ich mich an die Arbeit gemacht, meine Seite neu zu bauen.
|
||||||
|
Und gebaut.
|
||||||
|
Und gebaut.
|
||||||
|
Und gebaut...
|
||||||
|
|
||||||
|
Tja ich fing an zu arbeiten und plötzlich war da gar nicht mehr so viel Zeit.
|
||||||
|
|
||||||
|
Aber nach wirklich langer Zeit und viel Arbeit ist es jetzt endlich soweit - die neue Seite ist hübsch, sehr schnell, hat vollen Score bei Lighthouse und ist komplett in Astro gebaut.
|
||||||
|
Ich bin sehr zufrieden damit.
|
||||||
|
|
||||||
|
Vielleicht kommt irgendwann ja nochmal wieder ein Terminal dazu, dass ich früher als Webseite hatte.
|
||||||
|
Aber vorerst genieße ich es erstmal wieder eine eine hübsche und schnelle Seite zu haben.
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ const { entry } = Astro.props;
|
|||||||
const { Content } = await render(entry);
|
const { Content } = await render(entry);
|
||||||
---
|
---
|
||||||
<BaseLayout title={entry.data.title} theme="blog">
|
<BaseLayout title={entry.data.title} theme="blog">
|
||||||
<div class="glass-container header">
|
<div class="glass-container header" style={`view-transition-name: blog-header-${entry.id};`}>
|
||||||
<h1>{entry.data.title}</h1>
|
<h1 style={`view-transition-name: blog-headline-${entry.id};`}>{entry.data.title}</h1>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<time>{entry.data.pubDate.toLocaleDateString()}</time>
|
<time style={`view-transition-name: blog-pubdate-${entry.id};`}>{entry.data.pubDate.toLocaleDateString()}</time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ const posts = await getCollection('blog');
|
|||||||
posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||||
---
|
---
|
||||||
<BaseLayout title="Blog" theme="blog" description="Gedanken über Technologie, Design und das Leben von c0ntroller.de.">
|
<BaseLayout title="Blog" theme="blog" description="Gedanken über Technologie, Design und das Leben von c0ntroller.de.">
|
||||||
<div class="glass-container header">
|
<div class="glass-container header" style="view-transition-name:main-glass;">
|
||||||
<h1>Artikel & Gedanken</h1>
|
<h1>Artikel & Gedanken</h1>
|
||||||
<p>Gedanken über Technologie, Design und das Leben.</p>
|
<p>Gedanken über Technologie, Design und das Leben.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="list">
|
<div class="list">
|
||||||
{posts.map(post => (
|
{posts.map(post => (
|
||||||
<a href={`/blog/${post.id}`} class="glass-container link-card">
|
<a href={`/blog/${post.id}`} class="glass-container link-card" style={`view-transition-name: blog-header-${post.id};`}>
|
||||||
<h2>{post.data.title}</h2>
|
<h2 style={`view-transition-name: blog-headline-${post.id};`}>{post.data.title}</h2>
|
||||||
<p class="date">{post.data.pubDate.toLocaleDateString()}</p>
|
<time class="date" style={`view-transition-name: blog-pubdate-${post.id};`}>{post.data.pubDate.toLocaleDateString()}</time>
|
||||||
<p>{post.data.summary}</p>
|
<p>{post.data.summary}</p>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ const chapters = allPages.reduce((acc, p) => {
|
|||||||
}, {} as Record<string, typeof allPages>);
|
}, {} as Record<string, typeof allPages>);
|
||||||
---
|
---
|
||||||
<BaseLayout title={page.data.title} theme="book">
|
<BaseLayout title={page.data.title} theme="book">
|
||||||
<div class="glass-container content prose">
|
<div class="glass-container content prose" style="view-transition-name:main-glass;">
|
||||||
<h1>{page.data.title}</h1>
|
<h1 style={`view-transition-name: book-headline-${page.id.replaceAll("/", "-")};`}>{page.data.title}</h1>
|
||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const chapters = pages.reduce((acc, page) => {
|
|||||||
}, {} as Record<string, typeof pages>);
|
}, {} as Record<string, typeof pages>);
|
||||||
---
|
---
|
||||||
<BaseLayout title="Das Buch" theme="book" description="Ein fortlaufendes Werk über Software, Design und Architektur von c0ntroller.de.">
|
<BaseLayout title="Das Buch" theme="book" description="Ein fortlaufendes Werk über Software, Design und Architektur von c0ntroller.de.">
|
||||||
<div class="glass-container header">
|
<div class="glass-container header" style="view-transition-name:main-glass;">
|
||||||
<h1>Das Buch</h1>
|
<h1>Das Buch</h1>
|
||||||
<p>Ein fortlaufendes Werk über Software, Design und Architektur.</p>
|
<p>Ein fortlaufendes Werk über Software, Design und Architektur.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,7 +25,7 @@ const chapters = pages.reduce((acc, page) => {
|
|||||||
<h3>{chapter.replace(/-/g, ' ').toUpperCase()}</h3>
|
<h3>{chapter.replace(/-/g, ' ').toUpperCase()}</h3>
|
||||||
<ul class="page-list">
|
<ul class="page-list">
|
||||||
{items.map(item => (
|
{items.map(item => (
|
||||||
<li><a href={`/book/${item.id}`}>{item.data.title}</a></li>
|
<li><a href={`/book/${item.id}`} style={`view-transition-name: book-headline-${item.id.replaceAll("/", "-")};`}>{item.data.title}</a></li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import socials from "../data/socials.json";
|
|||||||
<div class="socials fade-in-up shadow-glow glass-bg glass-container">
|
<div class="socials fade-in-up shadow-glow glass-bg glass-container">
|
||||||
{
|
{
|
||||||
socials.map(({ name, url, icon }) => (
|
socials.map(({ name, url, icon }) => (
|
||||||
<a href={url} target="_blank" rel="noreferrer" class="social-link">
|
<a href={url} target="_blank" referrerpolicy="no-referrer" class="social-link">
|
||||||
<Icon name={icon} desc={name} />
|
<Icon name={icon} desc={name} />
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { getCollection, render } from 'astro:content';
|
import { getCollection, render } from 'astro:content';
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
import "katex/dist/katex.min.css";
|
import "katex/dist/katex.min.css";
|
||||||
import "rehype-callouts/theme/obsidian"
|
import "rehype-callouts/theme/obsidian"
|
||||||
@@ -15,15 +16,27 @@ const { entry } = Astro.props;
|
|||||||
const { Content } = await render(entry);
|
const { Content } = await render(entry);
|
||||||
---
|
---
|
||||||
<BaseLayout title={entry.data.title} theme="portfolio">
|
<BaseLayout title={entry.data.title} theme="portfolio">
|
||||||
<div class="glass-container header">
|
<div class="glass-container header" style={`view-transition-name: project-header-${entry.id};`}>
|
||||||
<h1>{entry.data.title}</h1>
|
<h1 style={`view-transition-name: project-headline-${entry.id};`}>{entry.data.title}</h1>
|
||||||
<p>{entry.data.summary}</p>
|
<p>{entry.data.summary}</p>
|
||||||
|
{(entry.data.tags || entry.data.repository) &&
|
||||||
|
(<div class="additional-meta">
|
||||||
|
<div class="meta-tags">
|
||||||
{entry.data.tags && (
|
{entry.data.tags && (
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
{entry.data.tags.map(tech => <span class="tag">{tech}</span>)}
|
{entry.data.tags?.map(tech => <span class="tag">{tech}</span>)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="meta-repository">
|
||||||
|
{ entry.data.repository && (
|
||||||
|
<a href={entry.data.repository} referrerpolicy="no-referrer">
|
||||||
|
<Icon name="simple-icons:git" style={`view-transition-name: project-git-${entry.id};`} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="glass-container content prose">
|
<div class="glass-container content prose">
|
||||||
<Content />
|
<Content />
|
||||||
@@ -39,6 +52,7 @@ const { Content } = await render(entry);
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.tag {
|
.tag {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -49,4 +63,23 @@ const { Content } = await render(entry);
|
|||||||
.content {
|
.content {
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
}
|
}
|
||||||
|
.additional-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.meta-repository {
|
||||||
|
flex-shrink: 1;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: rgb(var(--accent-base));
|
||||||
|
}
|
||||||
|
.meta-repository a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,26 +1,37 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
|
||||||
const projects = await getCollection('portfolio');
|
const projects = await getCollection('portfolio');
|
||||||
projects.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
projects.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
||||||
---
|
---
|
||||||
<BaseLayout title="Portfolio" theme="portfolio" description="Eine Auswahl aktueller Projekte und Experimente von c0ntroller.de.">
|
<BaseLayout title="Portfolio" theme="portfolio" description="Eine Auswahl aktueller Projekte und Experimente von c0ntroller.de.">
|
||||||
<div class="glass-container">
|
<div class="glass-container" style="view-transition-name:main-glass;">
|
||||||
<h1>Meine Arbeiten</h1>
|
<h1>Meine Arbeiten</h1>
|
||||||
<p>Eine Auswahl aktueller Projekte und Experimente.</p>
|
<p>Eine Auswahl aktueller Projekte und Experimente.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{projects.map(project => (
|
{projects.map(project => (
|
||||||
<a href={`/portfolio/${project.id}`} class="glass-container link-card">
|
<a href={`/portfolio/${project.id}`} class="glass-container link-card" style={`view-transition-name: project-header-${project.id};`}>
|
||||||
<h2>{project.data.title}</h2>
|
<h2 style={`view-transition-name: project-headline-${project.id};`}>{project.data.title}</h2>
|
||||||
<p>{project.data.summary}</p>
|
<p>{project.data.summary}</p>
|
||||||
|
{(project.data.tags || project.data.repository) &&
|
||||||
|
(<div class="additional-meta">
|
||||||
|
<div class="meta-tags">
|
||||||
{project.data.tags && (
|
{project.data.tags && (
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
{project.data.tags?.map(tech => <span class="tag">{tech}</span>)}
|
{project.data.tags?.map(tech => <span class="tag">{tech}</span>)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="meta-repository">
|
||||||
|
{ project.data.repository && (
|
||||||
|
<Icon name="simple-icons:git" style={`view-transition-name: project-git-${project.id};`} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -40,11 +51,22 @@ projects.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
|||||||
.link-card h2 {
|
.link-card h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
.additional-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-top: 1rem;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
flex-grow: 1;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.tag {
|
.tag {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -52,4 +74,11 @@ projects.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
|||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.meta-repository {
|
||||||
|
flex-shrink: 1;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: rgb(var(--accent-base));
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
transition: color .4s ease, background-color .4s ease, border-color .4s ease, box-shadow .4s ease;
|
transition: color .4s ease, background-color .4s ease, border-color .4s ease, box-shadow .4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
@view-transition {
|
@view-transition {
|
||||||
navigation: auto;
|
navigation: auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::view-transition-group(root) {
|
::view-transition-group(root) {
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
|
|||||||
Reference in New Issue
Block a user