<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Webdevelopment Archives - Mobile USTP MKL</title>
	<atom:link href="https://mobile.fhstp.ac.at/category/development/webdevelopment/feed/" rel="self" type="application/rss+xml" />
	<link>https://mobile.fhstp.ac.at/category/development/webdevelopment/</link>
	<description>Die &#34;Mobile Forschungsgruppe&#34; der USTP, sie  sammelt hier alles zu den Themen Design, UX und Entwicklung mobiler Applikationen</description>
	<lastBuildDate>Tue, 11 Aug 2026 20:42:21 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://mobile.fhstp.ac.at/wp-content/uploads/2025/03/icon-120x120.webp</url>
	<title>Webdevelopment Archives - Mobile USTP MKL</title>
	<link>https://mobile.fhstp.ac.at/category/development/webdevelopment/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Workshop &#124; Astro</title>
		<link>https://mobile.fhstp.ac.at/workshop/workshop-astro/</link>
		
		<dc:creator><![CDATA[Caroline Labres]]></dc:creator>
		<pubDate>Sun, 16 Nov 2025 17:20:52 +0000</pubDate>
				<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[Workshop]]></category>
		<category><![CDATA[Astro]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=15111</guid>

					<description><![CDATA[<p>Im 3. Semester habe ich einen Workshop zu Astro gehalten. Dabei habe ich erklärt, was das Webframework ausmacht, und anhand von einem praktischen Beispiel die wichtigsten Kernfunktionen demonstriert. Was ist Astro? Astro ist ein Framework für contentfokussierte Webprojekte, wie z.B. Blogs, E-Commerce- oder Marketing-Websites. Es setzt auf eine komponentenbasierte Web-Architektur, was bedeutet, dass das meiste <a class="read-more" href="https://mobile.fhstp.ac.at/workshop/workshop-astro/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/workshop/workshop-astro/">Workshop | Astro</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Im 3. Semester habe ich einen Workshop zu Astro gehalten. Dabei habe ich erklärt, was das Webframework ausmacht, und anhand von einem praktischen Beispiel die wichtigsten Kernfunktionen demonstriert.</p>



<h2 class="wp-block-heading">Was ist Astro?</h2>



<p class="wp-block-paragraph">Astro ist ein Framework für contentfokussierte Webprojekte, wie z.B. Blogs, E-Commerce- oder Marketing-Websites. Es setzt auf eine <strong>komponentenbasierte Web-Architektur</strong>, was bedeutet, dass das meiste als statische HTML-Seite gerendert wird und man kleine Islands mit JavaScript für Interaktivität hinzufügen kann. Astro verfolgt einen <strong>Server-first-Ansatz</strong> und kommt standardmäßig <strong>ohne JavaScript</strong> am Client aus, liefert aber bei Bedarf <strong>Unterstützung für Frameworks</strong> wie React, Vue, Svelte, Solid etc. Darüber hinaus bietet Astro <strong>Content Collections</strong>, mit denen sich Markdown-Inhalte strukturiert organisieren und mithilfe von TypeScript typsicher validieren lassen.</p>



<h2 class="wp-block-heading">Projekt erstellen</h2>



<p class="wp-block-paragraph"><code>npm create astro@latest</code> (basic, helpful starter Projekt auswählen)</p>



<p class="wp-block-paragraph">In VS Code gibt es eine Extension namens Astro, die hilfreich ist.</p>



<p class="wp-block-paragraph"> Ordnerstruktur (als Beispiel):</p>



<ul class="wp-block-list">
<li>assets</li>



<li>components</li>



<li>content (für Content Collections)</li>



<li>layouts</li>



<li>pages (file-based Routing)</li>



<li>styles</li>
</ul>



<h2 class="wp-block-heading">Dateiaufbau und Astro-Komponenten</h2>



<ul class="wp-block-list">
<li>Dateiendung: .astro</li>



<li>Codeausführung:
<ul class="wp-block-list">
<li>serverseitig: <code>--- ---</code> (Frontmatter)</li>



<li>clientseitig: <code>&lt;script>&lt;/script></code></li>
</ul>
</li>



<li>JSX-ähnliche Ausdrücke
<ul class="wp-block-list">
<li><code>{variable}</code> in HTML</li>



<li>Aber: Funktionen und Objekte können so nicht übergeben werden (wie z.B. bei React), stattdessen mit Script-Tag umsetzbar</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Beispielsweise MyAstroComp.astro:</p>



<pre class="wp-block-code"><code>---
// Script mit JS, das am Server ausgeführt wird
const text = "Hello World";
---
&lt;!-- Hier Template der Komponente mit HTML, CSS und JS --&gt;
&lt;div&gt;{text}&lt;/div&gt;</code></pre>



<p class="wp-block-paragraph">Was nicht funktioniert:</p>



<pre class="wp-block-code"><code>---
function handleClick() {
  console.log("clicked");
}
---
&lt;button onclick="handleClick()"&gt;{text}&lt;/button&gt;</code></pre>



<p class="wp-block-paragraph">Komponente in index.astro verwenden:</p>



<pre class="wp-block-code"><code>---
import MyAstroComp from "../components/MyAstroComp.astro";
---
&lt;MyAstroComp /&gt;</code></pre>



<ul class="wp-block-list">
<li><code>&lt;slot /></code> für Children (wie {children} in React)</li>



<li>Mit <code>Astro.props</code> auf Properties zugreifen</li>
</ul>



<pre class="wp-block-code"><code>---
interface Props {
  name: string;
}
const { name } = Astro.props;
---
&lt;h2&gt;Hello {name}!&lt;/h2&gt;</code></pre>



<h2 class="wp-block-heading">Unterschiede Astro vs. JSX</h2>



<figure class="wp-block-table"><table class="has-black-color has-text-color has-link-color has-fixed-layout"><thead><tr><th></th><th>Astro</th><th>JSX</th></tr></thead><tbody><tr><td>Attribute</td><td>kebab-case</td><td>camelCase</td></tr><tr><td>Mehrere Elemente</td><td>Ohne Parent zulässig</td><td>Nur in einem einzelnen &lt;div&gt; oder &lt;&gt; zulässig</td></tr><tr><td>Kommentare</td><td>HTML- und JS-Kommentare</td><td>JS-Kommentare</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">React hinzufügen</h2>



<p class="wp-block-paragraph">Interaktivität in Astro Komponenten einzubauen ist mühsam. Um mit einem Buttonklick umgehen zu können, müsste man ein &lt;script&gt; Tag hinzufügen. Stattdessen kann man aber auch ein beliebiges anderes Framework zum Projekt hinzufügen und so auch beispielsweise React-Komponenten bauen und nutzen.</p>



<p class="wp-block-paragraph">Um React als Beispiel hinzuzufügen: <code>npx astro add react</code></p>



<p class="wp-block-paragraph">Achtung: Komponente wird by default nur am Server gerendert!</p>



<p class="wp-block-paragraph">Für Interaktivität braucht es Hydration und eine Client Directive.</p>



<h3 class="wp-block-heading">Client Directives</h3>



<p class="wp-block-paragraph">Die client:* Directive gibt an, wann das JavaScript an den Browser geschickt werden soll. Die Komponente wird zuerst am Server gerendert (außer bei client:only), dann wird das JS gesendet und somit wird die Komponte hydrated und interaktiv.</p>



<ul class="wp-block-list">
<li><code>client: load</code>
<ul class="wp-block-list">
<li>JS: sofort beim Laden der Seite</li>
</ul>
</li>



<li><code>client: idle</code>
<ul class="wp-block-list">
<li>JS: nach initialem Ladeprozess und wenn requestIdleCallback-Event ausgelöst wird</li>
</ul>
</li>



<li><code>client: visible / client:visible={{rootMargin}}</code>
<ul class="wp-block-list">
<li>JS: sobald Komponente bzw. angegebener Margin sichtbar ist</li>
</ul>
</li>



<li><code>client:media={string}</code>
<ul class="wp-block-list">
<li>JS: wenn CSS media query erfüllt wird (z.B. nur für bestimmte Bildschirmgrößen)</li>
</ul>
</li>



<li><code>client:only={string}</code>
<ul class="wp-block-list">
<li>Überspringt SSR und rendert Komponente nur am Client. Framework muss als String übergeben werden.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Die Client Directive wird dort hinzufügt, wo die Komponente verwendet wird.</p>



<pre class="wp-block-code"><code>---
import MyReactButton from "../components/MyReactButton";
---
&lt;MyReactButton client:load /&gt;</code></pre>



<h2 class="wp-block-heading">Output Type für Build</h2>



<p class="wp-block-paragraph">In der astro.config Datei kann der Output Type festgelegt werden. Dieser bestimmt, wann die Seiten standardmäßig gerendert werden (beim Builden oder on demand). Das gilt für alle Seiten, außer bei denen eine Ausnahme festgelegt ist.</p>



<p class="wp-block-paragraph">In astro.config.mjs: <code>output: 'static'</code> oder <code>'server'</code></p>



<ul class="wp-block-list">
<li><code>static</code> (default): SSG, Seiten werden vorgerendert (<em>prerendered</em>); Ergebnis: statische Website</li>



<li><code>server</code>: SSR, Seiten werden on demand gerendert; Ergebnis: server-rendered Website</li>



<li>Für einzelne Seite ändern: <code>export const prerender = false</code>
<ul class="wp-block-list">
<li>wenn in config static: <code>false</code></li>



<li>wenn in config server: <code>true</code></li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Wichtig: Wenn eine Seite SSR verwendet, muss ein <strong>Server-Adapter</strong> (z.B. node) festgelegt werden: <code>npx astro add node</code></p>



<h2 class="wp-block-heading">Blog als Praxisbeispiel</h2>



<p class="wp-block-paragraph">Als Beispiel wird ein kleiner, einfacher Blog programmiert. Dieser beinhaltet zwei Seiten, wobei auf einer alle Einträge mit Bild, Datum und Titel aufgelistet werden (Übersichtsseite) und auf der anderen die Informationen des jeweiligen Eintrags stehen (Detailseite). Die einzelnen Blogbeiträge sollen in Form von Markdown-Dateien erstellt, bearbeitet und verwaltet werden können. Auf das Styling wird bei diesem Beispiel nicht eingegangen.</p>



<h3 class="wp-block-heading">Content Collection erstellen</h3>



<p class="wp-block-paragraph">Content Collections helfen dabei, Markdown-Dateien zu organisieren. Genau das wird für die Blogbeiträge benötigt. Um eine Collection zu erstellen, muss diese in src/content.config.ts definiert werden.</p>



<pre class="wp-block-code"><code>import { glob } from "astro/loaders";
import { defineCollection, z } from "astro:content";

const blog = defineCollection({
  loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
  schema: () =&gt;
    z.object({
      title: z.string(),
      date: z.coerce.date(),
      img: z.string(),
    }),
});

export const collections = { blog };</code></pre>



<p class="wp-block-paragraph">Die Funktion <code>defineCollection()</code> verlangt dabei zwei Parameter, einerseits den Loader und andererseits optional das Schema. Der Loader legt fest, wie und von wo die Inhalte der Collection geladen werden. Astro stellt dabei zwei integrierte Varianten zur Verfügung:</p>



<ul class="wp-block-list">
<li><code>glob()</code>: erstellt Einträge aus Ordnern mit Dateien</li>



<li><code>file()</code>: erstellt Einträge basierend auf einer lokalen Datei (z.B. JSON-File)</li>
</ul>



<p class="wp-block-paragraph">Das Schema wird mit zod erstellt. Wird ein Schema definiert, so werden die entsprechenden Markdown-Dateien dahingehend validiert. Außerdem können generierte Types verwendet werden (z.B. <code>type Props = CollectionEntry&lt;"blog"&gt;["data"];</code>). Wichtig ist nur, dass der Dev-Server neu gestartet wird, wenn sich Änderungen am Schema ergeben, sodass Astro das mitbekommt.</p>



<p class="wp-block-paragraph">Ein Blogeintrag soll in diesem Fall immer einen Titel, ein Datum und ein Bild beinhalten, sodass diese Informationen auf der Übersichtsseite angezeigt und auf der Detailseite immer gleich formatiert werden können.</p>



<h3 class="wp-block-heading">Blogbeiträge hinzufügen</h3>



<p class="wp-block-paragraph">Es können jetzt diverse Blogbeiträge in Form von Markdown-Dateien unter content/blog erstellt werden (Pfad in content.config.ts festgelegt).</p>



<p class="wp-block-paragraph">Beispiel: first-entry.md</p>



<pre class="wp-block-code"><code>---
title: "First Entry"
date: "2025-10-30"
img: "/favicon.svg"
---
Hello, this is my first entry.

## H2

This is a paragraph.</code></pre>



<p class="wp-block-paragraph">Im Frontmatter (innerhalb der drei Bindestriche) werden die drei Parameter angegeben, darunter folgt der Inhalt. Als Bild wird der Einfachheit halber das favicon im public-Ordner hergenommen. Der Blogbeitrag kann Überschriften, Absätze, Links, Codeteile, Tabellen und alles, was Markdown zu bieten hat, beinhalten. Wichtig ist nur, dass man das entsprechende Styling dafür definiert (h2, p, code, a etc.).</p>



<h4 class="wp-block-heading">MDX</h4>



<p class="wp-block-paragraph">Es können nicht nur md-Dateien erstellt werden, sondern auch mdx-Dateien, in denen die eigenen Komponenten (egal ob Astro, React etc.) verwendet werden können. Es muss nur die entsprechende Astro-Integration installiert werden (<code>npx astro add mdx</code>) und mdx in der Definition der Content Collection berücksichtigt werden (wurde bereits getan).</p>



<p class="wp-block-paragraph">Beispiel: second-entry.mdx</p>



<pre class="wp-block-code"><code>---
title: "Second Entry"
date: "2025-10-31"
img: "/favicon.svg"
---

This is my second entry.

import MyReactButton from "../../components/MyReactButton"

&lt;MyReactButton client:load /&gt;</code></pre>



<p class="wp-block-paragraph">components/MyReactButton.tsx</p>



<pre class="wp-block-code"><code>export default function MyReactButton(){

    function handleClick(){
        console.log("Hello, you clicked me")
    }

    return (
        &lt;button onClick={handleClick}&gt;Click me&lt;/button&gt;
    )
}</code></pre>



<h3 class="wp-block-heading">Seiten erstellen</h3>



<p class="wp-block-paragraph">Astro verwendet file-based Routing. Die Ordner- und Dateistruktur im pages-Ordner bestimmt somit automatisch, unter welchen URLs die Seiten aufgerufen werden können. Im pages-Ordner wird ein Unterordner blog und darin index.astro und [slug].astro angelegt. Der Blog ist somit unter /blog erreichbar. index.astro ist eine statische Route, während [slug].astro eine dynamische Route ist. Der Parameter slug kann dabei durch eine beliebige andere Bezeichnung ersetzt werden. Für mehr Tiefe kann auch ein Rest-Parameter verwendet werden: [&#8230;path].astro. </p>



<h4 class="wp-block-heading">Übersichtsseite</h4>



<p class="wp-block-paragraph">Für die index Seite wird zuerst ein Layout erstellt. Ein Layout ist dabei nichts anderes als eine normale Astro-Komponente.</p>



<p class="wp-block-paragraph">layouts/Layout.astro</p>



<pre class="wp-block-code"><code>---<br>import "../styles/global.css";<br>---<br><br>&lt;!doctype html&gt;<br>&lt;html lang="en"&gt;<br>  &lt;head&gt;<br>    &lt;meta charset="UTF-8" /&gt;<br>    &lt;meta name="viewport" content="width=device-width" /&gt;<br>    &lt;link rel="icon" type="image/svg+xml" href="/favicon.svg" /&gt;<br>    &lt;meta name="generator" content={Astro.generator} /&gt;<br>    &lt;title&gt;My Blog&lt;/title&gt;<br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;slot /&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;<br><br>&lt;style&gt;<br>  html,<br>  body {<br>    margin: 0;<br>    width: 100%;<br>    height: 100%;<br>  }<br>&lt;/style&gt;</code></pre>



<p class="wp-block-paragraph">Für den Header kann ebenso eine Astro-Komponente erstellt werden (components/Header.tsx).</p>



<pre class="wp-block-code"><code>&lt;header&gt;
  &lt;nav&gt;
    &lt;a href="/blog"&gt;My Blog&lt;/a&gt;
  &lt;/nav&gt;
&lt;/header&gt;
</code></pre>



<p class="wp-block-paragraph">Nun kann index.astro befüllt werden:</p>



<pre class="wp-block-code"><code>---
import { getCollection } from "astro:content";
import Header from "../../components/Header.astro";
import Layout from "../../layouts/Layout.astro";

const posts = (await getCollection("blog")).sort((a, b) =&gt; {
  return b.data.date.valueOf() - a.data.date.valueOf();
});
---

&lt;Layout&gt;
  &lt;Header /&gt;
  &lt;ul&gt;
    {
      posts.map((post) =&gt; (
        &lt;li&gt;
          &lt;a href={"/blog/" + post.id}&gt;
            &lt;div&gt;
              &lt;img src={post.data.img} /&gt;
            &lt;/div&gt;
            &lt;div&gt;{post.data.title}&lt;/div&gt;
            &lt;div&gt;{post.data.date.toLocaleDateString()}&lt;/div&gt;
          &lt;/a&gt;
        &lt;/li&gt;
      ))
    }
  &lt;/ul&gt;
&lt;/Layout&gt;</code></pre>



<p class="wp-block-paragraph">Mithilfe von <code>getCollection()</code> kann eine Collection, sprich ein Array von Einträgen, geholt werden. In diesem Beispiel werden die Einträge von der Blog-Collection zusätzlich nach dem Datum sortiert.</p>



<h4 class="wp-block-heading">Detailseite</h4>



<p class="wp-block-paragraph">Für die Detailseite wird ebenso ein Layout erstellt, das den Titel, das Bild und das Datum übergeben bekommt und anzeigt.</p>



<p class="wp-block-paragraph">layouts/BlogLayout.astro</p>



<pre class="wp-block-code"><code>---
import type { CollectionEntry } from "astro:content";
import Header from "../components/Header.astro";

type Props = CollectionEntry&lt;"blog"&gt;&#91;"data"];

const { title, date, img} = Astro.props;
---

&lt;!doctype html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width" /&gt;
    &lt;link rel="icon" type="image/svg+xml" href="/favicon.svg" /&gt;
    &lt;meta name="generator" content={Astro.generator} /&gt;
    &lt;title&gt;My Blog&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;Header /&gt;
    &lt;main&gt;
      &lt;article&gt;
        &lt;div&gt;
          &lt;img src={img} /&gt;
        &lt;/div&gt;
        &lt;div&gt;{date.toLocaleDateString()}&lt;/div&gt;
        &lt;h1&gt;{title}&lt;/h1&gt;
        &lt;div&gt;
          &lt;slot /&gt;
        &lt;/div&gt;
      &lt;/article&gt;
    &lt;/main&gt;
  &lt;/body&gt;
&lt;/html&gt;

&lt;style&gt;
  html,
  body {
    margin: 0;
    width: 100%;
    height: 100%;
  }
&lt;/style&gt;</code></pre>



<p class="wp-block-paragraph">Die Detailseite muss nun den Parameter slug auslesen und die entsprechenden Informationen dem BlogLayout übergeben. Es gibt zwei Varianten, das zu tun, je nachdem ob man SSR oder SSG verwenden möchte.</p>



<p class="wp-block-paragraph"><strong>Variante 1: SSR (on demand Rendering)</strong></p>



<pre class="wp-block-code"><code>---
import { render } from "astro:content";
import { getEntry } from "astro:content";
import BlogLayout from "../../layouts/BlogLayout.astro";

export const prerender = false;
const { slug } = Astro.params;

if (!slug) {
  return Astro.rewrite("/404");
}

const entry = await getEntry("blog", slug);

if (!entry) {
  return Astro.rewrite("/404");
}

const { Content } = await render(entry);
---

&lt;BlogLayout {...entry.data}&gt;
  &lt;Content /&gt;
&lt;/BlogLayout&gt;
</code></pre>



<p class="wp-block-paragraph">Mithilfe von <code>Astro.params</code> kann der Parameter slug ausgelesen werden. Mit <code>getEntry()</code> wird dann der entsprechende Eintrag geholt. Wenn es keinen passenden Eintrag gibt, wird 404 angezeigt. Um den Inhalt der Zielseite anzuzeigen, ohne die URL zu verändern, wird <code>Astro.rewrite()</code> genutzt.</p>



<p class="wp-block-paragraph"><strong>Variante 2: SSG (statische Seite)</strong></p>



<pre class="wp-block-code"><code>---
import type { GetStaticPaths } from "astro";
import { render } from "astro:content";
import BlogLayout from "../../layouts/BlogLayout.astro";
import { getCollection } from "astro:content";

export const getStaticPaths = (async () =&gt; {
  const entries = await getCollection("blog");

  return entries.map((entry) =&gt; ({
    params: { slug: entry.id },
    props: entry,
  }));
}) satisfies GetStaticPaths;

const entry = Astro.props;

const { Content } = await render(entry);
---

&lt;BlogLayout {...entry.data}&gt;
  &lt;Content /&gt;
&lt;/BlogLayout&gt;
</code></pre>



<p class="wp-block-paragraph">Soll eine dynamische Route statisch sein, so muss sie eine Funktion namens <code>getStaticPaths()</code> exportieren, die ein Array an Objekten mit dem property <code>params</code> zurückgibt. So werden alle möglichen Paths vordefiniert. In diesem Beispiel holt man sich also alle Einträge der Blog-Collection in der <code>getStaticPaths()</code>-Funktion und über die Properties (<code>Astro.props</code>) können dann die Informationen des einzelnen Eintrags ausgelesen werden.</p>



<h2 class="wp-block-heading">Fazit</h2>



<p class="wp-block-paragraph">Mit Astro können schnelle Websites erstellt werden. Wenn man bereits Erfahrung mit HTML, JSX oder React hat, so fällt einem der Einstieg in das Framework besonders leicht, da man viele vertraute Konzepte wiederfindet. Nur bei komplexeren Websites stößt Astro irgendwann auf seine Grenzen.</p>



<h2 class="wp-block-heading">Quelle</h2>



<p class="wp-block-paragraph"><a href="https://docs.astro.build/en">https://docs.astro.build/en</a></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://mobile.fhstp.ac.at/workshop/workshop-astro/">Workshop | Astro</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PrintToMobile &#124; TreeFund</title>
		<link>https://mobile.fhstp.ac.at/development/printtomobile-treefund/</link>
		
		<dc:creator><![CDATA[Sander Hahn]]></dc:creator>
		<pubDate>Fri, 17 Oct 2025 12:56:35 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[Print-to-mobile]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Webentwicklung]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14942</guid>

					<description><![CDATA[<p>Today’s world is becoming increasingly complicated – and increasingly grey. Pollution, conflict, and overconsumption are draining the color from our planet. The idea of TreeFund is simple: find an easy, meaningful way to help make our world greener again – one small step at a time. Idea The goal of TreeFund is to provide an <a class="read-more" href="https://mobile.fhstp.ac.at/development/printtomobile-treefund/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/printtomobile-treefund/">PrintToMobile | TreeFund</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Today’s world is becoming increasingly complicated – and increasingly grey. Pollution, conflict, and overconsumption are draining the color from our planet. The idea of TreeFund is simple: find an easy, meaningful way to help make our world greener again – one small step at a time.</p>



<h2 class="wp-block-heading">Idea</h2>



<p class="wp-block-paragraph">The goal of <em>TreeFund</em> is to provide an easy and transparent way for people to contribute to environmental restoration projects in St. Pölten. Its main focus is supporting tree planting initiatives and teaching new generations how to help our planet. The idea is to connect with individuals who want to make a difference and focus on their specific needs. People will first encounter <em>TreeFund </em>through posters and flyers placed around the city. A QR code on each will lead directly to the <em>TreeFund </em>website, where users can learn more, get involved, and track the impact of their contributions.</p>



<p class="wp-block-paragraph"></p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="595" height="842" data-id="14964" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Print2-5.jpg" alt="" class="wp-image-14964"/></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="720" height="1520" data-id="14963" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-134357_Camera-5.jpg" alt="" class="wp-image-14963"/></figure>
</figure>
</div>
</div>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>Implementation</strong></h2>



<p class="wp-block-paragraph">As the first step in the implementation process, I created a user persona: <em>Lisa Gruber</em>, a student at the <em>University of Applied Sciences St. Pölten</em>.</p>



<p class="wp-block-paragraph"><em>Lisa </em>is deeply environmentally conscious and eager to contribute to positive change. However, she often feels frustrated by the impersonal nature of many existing donation platforms. She wants to see the real impact of her actions and connect with initiatives that align with her values on a local level.</p>



<p class="wp-block-paragraph"></p>



<figure class="wp-block-image size-full"><img decoding="async" width="1787" height="1725" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Persona.png" alt="" class="wp-image-14966" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Persona.png 1787w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Persona-1536x1483.png 1536w" sizes="(max-width: 1787px) 100vw, 1787px" /></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">To personalize this process, the user (Lisa) first contacts TreeFund via a form describing their personal needs and where they want to see a part of the world a little greener. Only after the user is happy and the tree planting effort is accepted by the city, the payment process begins.&nbsp;</p>



<p class="wp-block-paragraph">To attract younger customers the design follows a minimalist aesthetic with clean whites and greens to emphasize the simplicity and positivity of the project’s goal. The design was first created in Figma with inspirations like Ecosia and TreeApp. My goal was to stick to a strict number of TailwindCSS colors. The website switches between Stone-50 and -100 for the background and Lime-400, -500, -600 and -800 for the green accents.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="387" height="144" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Frame-17.png" alt="" class="wp-image-14982"/></figure>
</div>


<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">After the design process, the actual website was implemented using React, Next.js, Tailwind CSS, and Lucide for icons.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1436" height="2724" data-id="14975" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1.jpg" alt="" class="wp-image-14975" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1.jpg 1436w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1-810x1536.jpg 810w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135253_Chrome-1-1080x2048.jpg 1080w" sizes="auto, (max-width: 1436px) 100vw, 1436px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1440" height="2724" data-id="14976" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1.jpg" alt="" class="wp-image-14976" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1.jpg 1440w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1-812x1536.jpg 812w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135326_Chrome-1-1083x2048.jpg 1083w" sizes="auto, (max-width: 1440px) 100vw, 1440px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1440" height="2920" data-id="14974" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome.jpg" alt="" class="wp-image-14974" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome.jpg 1440w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome-757x1536.jpg 757w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251017-135332_Chrome-1010x2048.jpg 1010w" sizes="auto, (max-width: 1440px) 100vw, 1440px" /></figure>
</figure>



<p class="wp-block-paragraph"><strong>Link to the Web Application:</strong> https://fhstp-print2mobil.vercel.app/</p>



<p class="wp-block-paragraph"><br><strong>Disclaimer: </strong><em>This is a personal project created for educational and illustrative purposes. It is not affiliated with any existing environmental organizations. The aim is to demonstrate how web technology can be used to promote sustainability and environmental awareness.</em></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/printtomobile-treefund/">PrintToMobile | TreeFund</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Print2Mobile &#124; AR &#8211; Web Application to project 3D Objects onto a Catalog</title>
		<link>https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/</link>
		
		<dc:creator><![CDATA[Jakob Laschober]]></dc:creator>
		<pubDate>Thu, 16 Oct 2025 12:43:41 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[AR]]></category>
		<category><![CDATA[Augmented Reality]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[mobile web]]></category>
		<category><![CDATA[Print-to-mobile]]></category>
		<category><![CDATA[Print2Mobile]]></category>
		<category><![CDATA[webdevelopment]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14874</guid>

					<description><![CDATA[<p>Have you ever seen a cool piece of furniture in a furniture store catalog? Maybe you wondered how the couch looks from behind, or how to view the table in 3D to better estimate its dimensions. Idea I tried to use the well-known store IKEA and its Catalog to implement my idea. This project aims <a class="read-more" href="https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/">Print2Mobile | AR &#8211; Web Application to project 3D Objects onto a Catalog</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Have you ever seen a cool piece of furniture in a furniture store catalog? Maybe you wondered how the couch looks from behind, or how to view the table in 3D to better estimate its dimensions.</p>



<p class="has-medium-font-size wp-block-paragraph"><strong>Idea</strong></p>



<p class="wp-block-paragraph">I tried to use the well-known store IKEA and its Catalog to implement my idea. This project aims to bridge the gap between physical catalogs and digital visualization. The core concept involves using QR codes placed on the product pages to link to an external web application. Once the user scans the code, this application can display a 3D model of the product above its corresponding picture in the physical catalog.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full is-resized is-style-default"><img loading="lazy" decoding="async" width="4080" height="3072" data-id="14937" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3.jpg" alt="" class="wp-image-14937" style="width:519px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3.jpg 4080w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3-1536x1157.jpg 1536w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/PXL_20251016_083759829-3-2048x1542.jpg 2048w" sizes="auto, (max-width: 4080px) 100vw, 4080px" /></figure>



<figure class="wp-block-image size-full is-resized is-style-default"><img loading="lazy" decoding="async" width="960" height="2142" data-id="14932" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642.jpg" alt="" class="wp-image-14932" style="width:221px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642.jpg 960w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642-688x1536.jpg 688w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103642-918x2048.jpg 918w" sizes="auto, (max-width: 960px) 100vw, 960px" /></figure>
</figure>



<p class="has-medium-font-size wp-block-paragraph"><strong>Implementation</strong></p>



<p class="wp-block-paragraph">For technologies, I used HTML, CSS and JavaScript. For the purpose of pattern recognition and tracking, I used the framework <a href="https://hiukim.github.io/mind-ar-js-doc/">mindAR.js</a>. For displaying the 3D Objects, I used the framework <a href="https://aframe.io/">A-Frame</a>. Sadly, the 3D objects that I used are very high fidelity and that impacts the performance of the application. So when you want to scan the furniture elements on the page, you have to be very careful, not to move too much or too quickly. It&#8217;s hosted on my own server as a docker instance using a Nginx web server.</p>



<figure class="wp-block-gallery has-nested-images columns-3 is-cropped wp-block-gallery-4 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img decoding="async" data-id="14910" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103732.jpg" alt="" class="wp-image-14910" style="width:306px;height:auto"/></figure>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="960" height="2142" data-id="14928" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1.png" alt="" class="wp-image-14928" style="width:305px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1.png 960w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1-688x1536.png 688w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103711-1-918x2048.png 918w" sizes="auto, (max-width: 960px) 100vw, 960px" /></figure>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="960" height="2142" data-id="14929" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739.jpg" alt="" class="wp-image-14929" style="width:321px;height:auto" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739.jpg 960w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739-688x1536.jpg 688w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Screenshot_20251016-103739-918x2048.jpg 918w" sizes="auto, (max-width: 960px) 100vw, 960px" /></figure>
</figure>



<p class="wp-block-paragraph">Link to the Webapplication: <a href="https://ikea-scanner.laschober.eu/">https://.ikea-scanner.laschober.eu</a></p>



<p class="wp-block-paragraph"><strong>Disclaimer:</strong> This project is <strong>not affiliated with, endorsed by, or in any way officially connected with IKEA</strong>. The use of IKEA&#8217;s name and catalog images is solely for illustrative purposes as a practical example for the proof of concept.</p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/webdevelopment/print2mobile-ar-web-application-to-project-3d-objects-onto-a-catalog/">Print2Mobile | AR &#8211; Web Application to project 3D Objects onto a Catalog</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Print2Mobile &#124; Dishcovery</title>
		<link>https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/</link>
		
		<dc:creator><![CDATA[Kevin Kraushofer]]></dc:creator>
		<pubDate>Thu, 16 Oct 2025 10:31:34 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Studium]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14879</guid>

					<description><![CDATA[<p>(QR Codes in the pictures have no functionality) We’ve all been there, standing in front of the fridge or in the supermarket aisle not knowing what to cook. Meanwhile, tons of perfectly good food end up in the trash every day. Dishcovery aims to change that.With a simple scan at your local supermarket or your <a class="read-more" href="https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/">Print2Mobile | Dishcovery</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph" style="font-size:9px">(QR Codes in the pictures have no functionality)</p>



<p class="wp-block-paragraph">We’ve all been there, standing in front of the fridge or in the supermarket aisle not knowing what to cook. Meanwhile, tons of perfectly good food end up in the trash every day.</p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-8f761849 wp-block-group-is-layout-flex">
<p class="wp-block-paragraph"><strong>Dishcovery</strong> aims to change that.<br>With a simple scan at your local supermarket or your advertising in the letterbox, the app instantly shows you recipes that match your preferences <em>and</em> the store’s current stock including products nearing their expiry date. But that’s not all: you can also add ingredients you already have at home, and Dishcovery will automatically include them in the recipe suggestions. Every recipe comes with clear quantities, can be saved to your favorites and added directly to a digital shopping list. The app also shows how many products you’ve helped rescue, turning sustainability into something measurable and rewarding.</p>
</div>



<p class="wp-block-paragraph"></p>



<figure class="wp-block-gallery has-nested-images columns-4 is-cropped wp-block-gallery-5 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14898" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Homepage-1.png" alt="figma prototype Homepage recipe" class="wp-image-14898"/></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14899" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/homepage2.png" alt="figma prototype Homepage ingredients" class="wp-image-14899"/></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14897" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Filter.png" alt="figma prototype filterpage overview" class="wp-image-14897"/></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="393" height="852" data-id="14896" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/Filter2.png" alt="figma prototype filterpage whats in your kitchen page." class="wp-image-14896"/></figure>
</figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Dishcovery not only inspires creativity in the kitchen — it makes sustainability simple, digital, and rewarding.</p>



<p class="wp-block-paragraph"></p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="840" height="1188" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/website-mit-fake-qr-code-1-1.jpg" alt="" class="wp-image-14919" style="width:376px;height:auto"/></figure>
</div>


<p class="wp-block-paragraph"><strong>This project is not affiliated with BILLA or the REWE Group in any way. It is a purely academic student project, created for educational purposes only. No financial support, sponsorship, or compensation has been received from BILLA or any other company. Any use of names or logos is solely for demonstration purposes within the prototype.</strong></p>



<p class="wp-block-paragraph">iPhone design by Luis Orea<br>Icons designed by Kryston Schwarze https://coolicons.cool/<br>Illustrations/Icons designed by Freepick www.freepik.com</p>



<h2 class="wp-block-heading">Other Projects</h2>



<p class="wp-block-paragraph"><a href="https://swiva.app">Swiva</a> is a mobile app for saving ideas, places, recipes and activities from TikTok, Instagram, Maps and the web, and actually turning them into plans.</p>
<p>The post <a href="https://mobile.fhstp.ac.at/studium/print2mobile-dishcovery/">Print2Mobile | Dishcovery</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Projekt &#124; Powda Club-Dashboard</title>
		<link>https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/</link>
		
		<dc:creator><![CDATA[David Grünberger]]></dc:creator>
		<pubDate>Thu, 16 Oct 2025 01:25:15 +0000</pubDate>
				<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Dokumentation]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[Studium]]></category>
		<category><![CDATA[User Experience]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[clerk]]></category>
		<category><![CDATA[convex]]></category>
		<category><![CDATA[Next.js]]></category>
		<category><![CDATA[Semesterprojekt]]></category>
		<category><![CDATA[shadcn]]></category>
		<category><![CDATA[stripe]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[webhooks]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14812</guid>

					<description><![CDATA[<p>Im Rahmen des Semesterprojekts im zweiten Semester der Masterklasse Mobile habe ich ein Club Dashboard entwickelt – eine Webanwendung, mit der Skiclubs ihre Ausfahrten verwalten und Teilnehmer:innen direkt online buchen und bezahlen lassen können.Ziel des Projekts war es, einen funktionsfähigen MVP (Minimum Viable Product) zu erstellen, der eine vollständige End-to-End-Integration für Zahlungen bietet – von <a class="read-more" href="https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/">Projekt | Powda Club-Dashboard</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Im Rahmen des Semesterprojekts im zweiten Semester der Masterklasse Mobile habe ich ein <strong>Club Dashboard</strong> entwickelt – eine Webanwendung, mit der Skiclubs ihre <strong>Ausfahrten verwalten</strong> und <strong>Teilnehmer:innen direkt online buchen und bezahlen</strong> lassen können.<br>Ziel des Projekts war es, einen <strong>funktionsfähigen MVP (Minimum Viable Product)</strong> zu erstellen, der eine vollständige End-to-End-Integration für Zahlungen bietet – von der Erstellung der Events bis zur erfolgreichen Bezahlung über die Website.</p>



<p class="wp-block-paragraph">In diesem Beitrag möchte ich den Aufbau der Anwendung, die verwendeten Technologien sowie einige Herausforderungen und Learnings während der Umsetzung vorstellen.</p>



<h2 class="wp-block-heading">Technologie-Stack</h2>



<p class="wp-block-paragraph">Das Club Dashboard wurde als moderne Webanwendung mit Fokus auf Skalierbarkeit, Sicherheit und einfache Erweiterbarkeit entwickelt.<br>Hier ein Überblick über die zentralen Technologien:</p>



<h3 class="wp-block-heading">Framework: Next.js (App Router)</h3>



<p class="wp-block-paragraph">Für das Frontend kam <strong>Next.js</strong> mit dem neuen <strong>App Router</strong> zum Einsatz. Diese Architektur erlaubt serverseitiges Rendering (SSR), API-Routen und eine saubere Trennung von Client- und Server-Komponenten. Dadurch konnte ich sowohl UI-Logik als auch Backend-Funktionalität innerhalb eines Frameworks umsetzen.</p>



<p class="wp-block-paragraph"><strong>Vorteile:</strong></p>



<ul class="wp-block-list">
<li>Serverseitiges Rendering für bessere Performance und SEO</li>



<li>Integrierte API-Routen für serverseitige Logik (z. B. Payment Handling)</li>



<li>Moderne Struktur durch den App Router</li>
</ul>



<h3 class="wp-block-heading">Data Layer: Convex</h3>



<p class="wp-block-paragraph">Anstatt einer klassischen REST- oder GraphQL-API habe ich mich für <strong>Convex</strong> entschieden – eine innovative Backend-as-a-Service-Lösung, die reaktive Datenabfragen, serverseitige Funktionen und Auth-Integration out of the box bietet.</p>



<p class="wp-block-paragraph"><strong>Convex</strong> ermöglicht es, Daten- und Serverlogik in TypeScript zu schreiben und nahtlos mit dem Frontend zu verbinden. Dadurch entfällt ein Großteil der sonst notwendigen API- und Datenbankkonfiguration.</p>



<h3 class="wp-block-heading">Styling &amp; UI: TailwindCSS &amp; shadcn</h3>



<p class="wp-block-paragraph">Für das Styling habe ich auf <strong>TailwindCSS</strong> gesetzt – ein Utility-First-CSS-Framework, das schnelles Prototyping und einheitliches Design ermöglicht.<br>Zusätzlich kam <strong>shadcn</strong> zum Einsatz, eine leichtgewichtige Component Library mit Fokus auf Barrierefreiheit, Dark Mode und Customizability.</p>



<p class="wp-block-paragraph"><strong>Vorteile dieser Kombination:</strong></p>



<ul class="wp-block-list">
<li>Konsistentes UI mit klarer Design-Sprache</li>



<li>Wiederverwendbare Komponenten</li>



<li>Effiziente Entwicklung durch Utility-Klassen</li>



<li>Code Ownership: Components werden einfach ins Projekt kopiert</li>
</ul>



<h3 class="wp-block-heading">Authentifizierung: Clerk</h3>



<p class="wp-block-paragraph">Die Authentifizierung wird durch <strong>Clerk</strong> umgesetzt – denselben Anbieter, den ich bereits im ersten Semesterprojekt genutzt habe. Clerk bietet ein modernes User Management inklusive OAuth-Integration und JWT-Handling, was eine einfache Verbindung mit Convex und Next.js ermöglicht.</p>



<p class="wp-block-paragraph">Durch Clerk konnte ich schnell eine sichere Login-, Logout- und Session-Logik aufsetzen, ohne selbst komplexe Authentifizierungssysteme implementieren zu müssen.</p>



<h3 class="wp-block-heading">Payment Provider: Stripe</h3>



<p class="wp-block-paragraph">Ein zentrales Feature dieses Projekts war die <strong>Integration von Stripe</strong> als Payment Provider. Nutzer:innen können direkt über die Website eine Skiausfahrt buchen und bezahlen.<br>Dazu wurde das Stripe Checkout-System in die App eingebunden – inklusive <strong>Webhook-Handling</strong>, um Payment-Status-Änderungen serverseitig zu verarbeiten.</p>



<p class="wp-block-paragraph"><strong>Beispielhafte Flows:</strong></p>



<ul class="wp-block-list">
<li>User wählt eine Ausfahrt aus → Klick auf „Buchen“</li>



<li>Stripe Checkout öffnet sich → Zahlung erfolgt</li>



<li>Stripe sendet einen Webhook an das System → Buchung wird bestätigt und gespeichert</li>
</ul>



<p class="wp-block-paragraph">Diese End-to-End-Integration war der Kern des Projekts und erforderte ein gutes Zusammenspiel zwischen <strong>Frontend, Convex Backend und Stripe Webhooks</strong>.</p>



<h2 class="wp-block-heading">Kommunikation &amp; Webhooks</h2>



<p class="wp-block-paragraph">Ein spannender Teil des Projekts war die <strong>Kommunikation zwischen den verschiedenen Systemen</strong>.<br>Sowohl Stripe als auch Clerk arbeiten mit <strong>Webhooks</strong>, um Echtzeit-Informationen an das System zu senden (z. B. erfolgreiche Zahlungen oder neue Benutzer).<br>Die Webhooks werden über API-Routen in Next.js empfangen und anschließend in Convex weiterverarbeitet – beispielsweise um Buchungsdaten zu aktualisieren oder Rechnungen zu speichern.</p>



<h2 class="wp-block-heading">Herausforderungen &amp; Learnings</h2>



<p class="wp-block-paragraph">Während der Entwicklung gab es mehrere interessante Herausforderungen:</p>



<ul class="wp-block-list">
<li><strong>Webhook-Handling:</strong> Das Zusammenspiel von Stripe, Clerk und Convex erfordert saubere Trennung von Sicherheits- und Logikschichten. Insbesondere das Validieren von Webhook-Signaturen war anfänglich fehleranfällig.</li>



<li><strong>Datenmodellierung in Convex:</strong> Da Convex ein eigenes Datenmodell nutzt, war es notwendig, sich in dessen Schema-System einzuarbeiten.</li>



<li><strong>SSR + Client-Komponenten:</strong> Der Wechsel zwischen Server- und Client-Komponenten im Next.js App Router war zunächst ungewohnt und erforderte ein gutes Verständnis des Rendering-Modells.</li>
</ul>



<p class="wp-block-paragraph">Trotz dieser Hürden war die Entwicklung äußerst lehrreich – vor allem im Hinblick auf <strong>Payment-Flows, Security und API-Kommunikation</strong>.</p>



<h2 class="wp-block-heading">Fazit</h2>



<p class="wp-block-paragraph">Das Club Dashboard war ein spannendes Projekt, das mir erstmals die Umsetzung einer <strong>vollständigen End-to-End-Integration mit Stripe</strong> ermöglicht hat.<br>Durch den Einsatz moderner Tools wie <strong>Next.js, Convex, Clerk und Stripe</strong> konnte ich einen funktionalen MVP umsetzen, der reale Buchungsprozesse abbildet.</p>



<p class="wp-block-paragraph">Besonders interessant war zu sehen, wie gut sich die verschiedenen Services über <strong>Webhooks und API-Routen</strong> integrieren lassen – und wie wichtig saubere Datenflüsse und Sicherheit in einem Payment-System sind.</p>



<p class="wp-block-paragraph">Im nächsten Schritt plane ich, das Dashboard um weitere Funktionalitäten zu erweitern, um einem produktionsreifen Produkt näher zu kommen, das sich veröffentlichen und vertreiben lässt.</p>



<h2 class="wp-block-heading">Screenshots</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1996" height="1594" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/landing_page.jpg" alt="" class="wp-image-14844" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/landing_page.jpg 1996w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/landing_page-1536x1227.jpg 1536w" sizes="auto, (max-width: 1996px) 100vw, 1996px" /><figcaption class="wp-element-caption">Screenshot: Landing Page</figcaption></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1993" height="1591" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/ausfahrten.png" alt="" class="wp-image-14845" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/ausfahrten.png 1993w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/ausfahrten-1536x1226.png 1536w" sizes="auto, (max-width: 1993px) 100vw, 1993px" /><figcaption class="wp-element-caption">Screenshot: Ausfahrtenliste mit Buchungsoption</figcaption></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2011" height="1600" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/dashboard.png" alt="" class="wp-image-14847" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/dashboard.png 2011w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/dashboard-1536x1222.png 1536w" sizes="auto, (max-width: 2011px) 100vw, 2011px" /><figcaption class="wp-element-caption">Screenshot: Ausfahrtenverwaltung am Dashboard</figcaption></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1990" height="1429" src="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/zahlungsinfos.png" alt="" class="wp-image-14848" srcset="https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/zahlungsinfos.png 1990w, https://mobile.fhstp.ac.at/wp-content/uploads/2025/10/zahlungsinfos-1536x1103.png 1536w" sizes="auto, (max-width: 1990px) 100vw, 1990px" /><figcaption class="wp-element-caption">Screenshot: Eingebettete Stripe Account Komponenten am Dashboard</figcaption></figure>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/projekt-powda-club-dashboard/">Projekt | Powda Club-Dashboard</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Architecture of large frontend applications</title>
		<link>https://mobile.fhstp.ac.at/development/architecture-of-large-frontend-applications/</link>
		
		<dc:creator><![CDATA[Jan Weiß]]></dc:creator>
		<pubDate>Thu, 02 Oct 2025 06:06:11 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14804</guid>

					<description><![CDATA[<p>Creating web applications with the size of a prototype is easy. Things get spicy when a frontend application has been under active development by multiple teams and for many years. Ideally, the right measures were taken at an early stage of a project, which allows it to grow in an ordered way. Anyway, it is <a class="read-more" href="https://mobile.fhstp.ac.at/development/architecture-of-large-frontend-applications/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/architecture-of-large-frontend-applications/">Architecture of large frontend applications</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Creating web applications with the size of a prototype is easy. Things get spicy when a frontend application has been under active development by multiple teams and for many years. Ideally, the right measures were taken at an early stage of a project, which allows it to grow in an ordered way. Anyway, it is never too late. With this article, I would like to present my learning on this topic, which resulted from working on such a large frontend application.</p>



<h2 class="wp-block-heading"><strong>Modularization</strong></h2>



<p class="wp-block-paragraph">When you start a new Next or Nuxt application, you are tempted to throw all pages into the folder “pages” and all components into a folder “components,” etc. This will become a horrible mess once the application grows. My first and most important recommendation is to modularize the application ideally from the start. Each module holds its relevant code (pages, components, etc.).</p>



<p class="wp-block-paragraph">It can be tricky to decide how to draw your modules. You can read the book “The Art of Micro Frontends” by Florian Rappl, which covers this topic in a chapter. The approach which has worked for me and my team was to first identify the domains of your application and make them modules. Later, you can split the code of the domains into further sub-modules representing individual features. With this clear structure, you will be able to navigate your code base easily and to see and enforce dependencies/relationships between the modules of your project.</p>



<p class="wp-block-paragraph">You might want to convert some modules into micro-frontends, which are loaded at runtime. But be aware of the extra complexity which comes with micro-frontends. Again, I recommend reading the book “The Art of Micro Frontends” by Florian Rappl, which is a comprehensive guide in this regard.</p>



<h2 class="wp-block-heading"><strong>SOLID</strong></h2>



<p class="wp-block-paragraph">When designing software, keeping the SOLID principles in mind is essential. Yet, it seems that there is only little awareness of this among web developers. For creating the architecture of your project, I want to especially highlight two principles.</p>



<p class="wp-block-paragraph">Open/Closed Principle -> software entities should be open for extension but closed for modifications.<br>Dependency Inversion Principle -> depend upon abstractions, not concretes.</p>



<p class="wp-block-paragraph">They will lead to a special approach when designing your code. Let me give you an example. In an e-commerce application, you might have a module holding the code for displaying a product and another module which holds an add-to-cart button. Now, according to the principles, you would create your product view to contain and expose a slot which receives the product data. The module containing the add-to-cart button registers the button inside the product view’s slot. The result is that the module with the product view is extended without having to have knowledge of the add-to-cart button.</p>



<h2 class="wp-block-heading"><strong>Feature flags</strong></h2>



<p class="wp-block-paragraph">You modularized your application, and the code which belongs to the individual domains and features is in the respective location. You might have noticed that this structure encourages using feature flags to enable/disable modules. Feature flags might be necessary because your application is supposed to be distributed in different configurations. But feature flags have another interesting and powerful use: releasing features by activating a feature flag instead of by deploying your application. The idea is, when you are ready to release a feature, you deploy the application without the feature being activated. For more detailed information on the benefits and additional use cases of feature flags, please read the article &#8220;Feature Toggles (aka Feature Flags)“ by Pete Hodgson (<a href="https://martinfowler.com/articles/feature-toggles.html">https://martinfowler.com/articles/feature-toggles.html</a>).</p>



<h2 class="wp-block-heading"><strong>What’s next</strong></h2>



<p class="wp-block-paragraph">I hope this article gave you good ideas on how to design your application. I am looking forward to comments and discussion as this is a very relevant topic to me. This article might require follow-ups which are dedicated to specific technical implementations of the described principles.</p>
<p>The post <a href="https://mobile.fhstp.ac.at/development/architecture-of-large-frontend-applications/">Architecture of large frontend applications</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Projekt &#124; Recipe Scanner</title>
		<link>https://mobile.fhstp.ac.at/studium/studium-projekte/projekt-recipe-scanner/</link>
		
		<dc:creator><![CDATA[Jan Weiß]]></dc:creator>
		<pubDate>Wed, 01 Oct 2025 20:20:45 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Webdevelopment]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web-App]]></category>
		<guid isPermaLink="false">https://mobile.fhstp.ac.at/?p=14785</guid>

					<description><![CDATA[<p>Do you have a collection of printed or handwritten recipes you’d like to digitize? You’ve come to the right place! This tool makes it easy to convert photos of your recipes into a machine-readable format. The output format is a subset of the&#160;schema.org Recipe&#160;and can be expanded (merge request are welcome). How it works The <a class="read-more" href="https://mobile.fhstp.ac.at/studium/studium-projekte/projekt-recipe-scanner/">[...]</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/studium/studium-projekte/projekt-recipe-scanner/">Projekt | Recipe Scanner</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Do you have a collection of printed or handwritten recipes you’d like to digitize? You’ve come to the right place!</p>



<p class="wp-block-paragraph"><a href="https://github.com/realJanWeiss/recipe-scanner">This tool</a> makes it easy to convert photos of your recipes into a machine-readable format.</p>



<p class="wp-block-paragraph">The output format is a subset of the&nbsp;<a href="https://schema.org/Recipe">schema.org Recipe</a>&nbsp;and can be expanded (merge request are welcome).</p>



<h2 class="wp-block-heading">How it works</h2>



<p class="wp-block-paragraph">The Recipe Scanner is a web application built with the Nuxt framework. Through its user interface, you can upload photos of recipes, which are stored on the server. The server then sends the images, along with a prompt, to a vision Large Language Model (LLM). The LLM extracts the relevant recipe information and formats it as specified. Finally, the server validates the structure of the returned data and sends it back to the client.</p>



<h2 class="wp-block-heading">Technical considerations</h2>



<p class="wp-block-paragraph">This application was created for personal use (I wanted to digitize many old and messy recipes from my school days) and for experimentation so it is not designed to be fully “production-ready.” This influenced some of my technical choices.</p>



<p class="wp-block-paragraph">For instance, I wanted to try out running a vision LLM on my own machine instead of using a hosted service. I tried out <a href="https://ollama.com/">Ollama</a> and <a href="https://lmstudio.ai/">LM Studio</a> and opted for the latter because its interface made managing connections easier. In principle, Ollama should also work.</p>



<p class="wp-block-paragraph">I also tried out several models. I began with the smalles vision-enabled Gemma 3 model (4B parameters, 3.3 GB). Unfortunately, it made many errors when extracting recipe data. Hosted models such as ChatGPT-5 and Mistral (Large?) performed much better, proving the task was feasible for LLMs. Among open-weight models, Mistral Small 3.2 (24B parameters, 15 GB) was the first that consistently produced accurate results. It’s possible that bigger Gemma 3 models (e.g., 12B) or specialized models like LLaVA, could also perform well or even better. If you test them, I’d be interested in hearing your results.</p>



<p class="wp-block-paragraph">Another unusual design choice is how persistence is handled. Since this is a personal project, I opted for simplicity. The server does not use a database. There are no user accounts and images and parsed recipe data are saved directly to the server&#8217;s file system. To simulate a multi-user experience, each user only sees their own recipes. This is managed on the client side by storing image IDs in the browser’s IndexedDB. IndexedDB is not the ideal tool for this, but I wanted to experiment with it.</p>



<h2 class="wp-block-heading">Next steps</h2>



<p class="wp-block-paragraph">My immediate goal is to digitize my collection of printed and handwritten recipes. Once that data is gathered, I plan to use both the images and extracted recipe information to fine-tune a smaller, more efficient model specifically optimized for this task.</p>



<p class="wp-block-paragraph">Link to the repository: <a href="https://github.com/realJanWeiss/recipe-scanner">https://github.com/realJanWeiss/recipe-scanner</a></p>
<p>The post <a href="https://mobile.fhstp.ac.at/studium/studium-projekte/projekt-recipe-scanner/">Projekt | Recipe Scanner</a> appeared first on <a href="https://mobile.fhstp.ac.at">Mobile USTP MKL</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
