svelteBlog Svelte Themes

Svelteblog

A simple SvelteBlog used for teaching Svelte

sv

Everything you need to build a Svelte project, powered by sv.

Creating a project

If you're seeing this, you've probably already done this step. Congrats!

# create a new project
npx sv create my-app

To recreate this project with the same configuration:

# recreate this project
npx sv create --template minimal --types ts --install npm svelteBlog

Developing

Once you've created a project and installed dependencies with npm install (or pnpm install or yarn), start a development server:

npm run dev

# or start the server and open the app in a new browser tab
npm run dev -- --open

Building

To create a production version of your app:

npm run build

You can preview the production build with npm run preview.

To deploy your app, you may need to install an adapter for your target environment.

Lysbilder

Steg 1 - Blogg index

etter commit 508ff6194508224425b6169df55f1b7942569ff7

/src/routes/+layout.svelte
+<slot></slot>
/src/routes/+page.svelte
+<script lang="ts">
+    import type { Article } from "$lib/types";
+    import {articlesCache} from "$lib/data/articlesCache";
+
+    let articles : Article[] = $state([]);
+    let isLoading = $state(true);
+
+    $effect(() => {
+        articles = articlesCache;
+        isLoading = false;
+    })
+</script>
+
+<main class="blog-index">
+    {#if isLoading}
+        <div class="loading">Laster artikler...</div>
+    {/if}
+
+    {#if articles && articles.length > 0}
+        <div class="article-grid">
+            {#each articles as article}
+                <div class="article-card">
+                    <h2><a href="/articles/{article.id}">{article.title}</a></h2>
+                    <p>{article.preamble}</p>
+                </div>
+            {/each}
+        </div>
+    {:else}
+        <div class="no-articles">Ingen artikler funnet.</div>
+    {/if}
+
+</main>
+
+<style>
+    .loading {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 100vh;
+    }
+
+    .article-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, 300px);
+        max-width: 1000px;
+        margin: 0 auto;
+        gap: 2rem;
+    }
+</style>

Steg 2 - Oppretter shared state for artikler og XHR fetch

/src/lib/state/BloggState.svelte.ts
+import type {Article} from "$lib/types";
+
+export const wait = async (amount: number)   => new Promise(res => setTimeout(res, amount ?? 100));
+
+export class BloggState {
+    articles= $state<Article[]>([]);
+    isLoading = $state<boolean>(false);
+    error =  $state<string | null>(null);
+    isLoaded = $state<boolean>(false);
+
+    async loadArticles(reload: boolean = false) {
+        if (this.isLoaded || reload) {
+            return this.articles;
+        }
+
+        this.isLoading = true;
+        this.error = null;
+
+        let rawResponse = await fetch('/api/articles');
+
+        if (rawResponse.ok && rawResponse) {
+            let content = await rawResponse.json();
+            this.articles = content.articles;
+            this.isLoaded = true;
+        } else {
+            this.error = "Feil ved lasting av artikler: " + rawResponse.statusText;
+        }
+
+        await wait(1000);
+        this.isLoading = false;
+    }
+
+    async loadArticle(id: string) {
+        if (!this.isLoaded) {
+            await this.loadArticles();
+        }
+
+        return this.articles.find(article => article.id === id);
+    }
+}
+
+export const bloggState = new BloggState();
/src/routes/+page.svelte
 <script lang="ts">
     import type { Article } from "$lib/types";
-    import {articlesCache} from "$lib/data/articlesCache";
+    import { bloggState} from "$lib/state/BloggState.svelte";
 
     let articles : Article[] = $state([]);
-    let isLoading = $state(true);
 
     $effect(() => {
-        articles = articlesCache;
-        isLoading = false;
-    })
+        bloggState.loadArticles();
+    });
+
+    $effect(() => {
+        articles = bloggState.articles;
+    });
 </script>
 
 <main class="blog-index">
-    {#if isLoading}
+    {#if bloggState.isLoading}
         <div class="loading">Laster artikler...</div>
     {/if}
     

Steg 3 - Implementerer en enkel side header som en komponent

/src/lib/components/Header.svelte
+<section class="header-area">
+  <div class="site-name">Svelte Blog</div>
+</section>
+
+<style>
+  .header-area {
+    background: var(--bg-primary);
+    border-bottom: 1px solid var(--border-default);
+    padding: 1.5rem 2rem;
+    display: flex;
+    align-items: center;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+    position: relative;
+    z-index: 100;
+  }
+
+  .site-name {
+    font-size: clamp(1.5rem, 4vw, 2.25rem);
+    font-weight: 700;
+    color: var(--text-primary);
+    margin: 0;
+    letter-spacing: -0.5px;
+    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05);
+    transition: color 0.2s ease;
+  }
+
+  .site-name:hover {
+    color: var(--text-accent);
+  }
+
+  /* Responsiv: mindre skjermer */
+  @media (max-width: 768px) {
+    .header-area {
+      padding: 1rem;
+    }
+
+    .site-name {
+      font-size: clamp(1.3rem, 6vw, 2rem);
+    }
+  }
+</style>
/src/routes/+layout.svelte
+<script>
+    import Header from "$lib/components/Header.svelte";
+</script>
+
+<Header></Header>
+
 <slot></slot>

Steg 4 - Implementerer Blogg artikkel

/src/lib/components/Markdown.svelte
+<script lang="ts">
+    import {marked} from "marked";
+
+    let {
+        toHtml
+    } = $props();
+
+    marked.use({
+        mangle: false,
+        headerIds: false
+    });
+</script>
+
+{#if toHtml}
+    {@html marked(toHtml) }
+{/if}
/src/lib/state/BloggState.svelte.ts
         export class BloggState {
             await this.loadArticles();
         }
 
-        return this.articles.find(article => article.id === id);
+        let article = this.articles.find(article => article.id === id);
+        if (article) {
+            return article;
+        } else {
+            return null;
+        }
     }
 }
/src/routes/+layout.svelte
 
 <Header></Header>
 
-<slot></slot>
\ No newline at end of file
+<slot></slot>
+
+<style>
+    :global(a) {
+        text-decoration: none;
+        color: inherit; /* Optional: to keep the link color consistent with surrounding text */
+    }
+
+    :global(a:hover) {
+        color: #007bff; /* Optional: change color on hover */
+    }
+
+    :global(a:visited) {
+        color: inherit; /* Optional: change color for visited links */
+    }
+</style>
/src/routes/articles/[articleId]/+page.svelte
+<script lang="ts">
+    import type { Article } from "$lib/types";
+    import { bloggState} from "$lib/state/BloggState.svelte.js";
+    import { page } from '$app/state';
+    import {onMount} from "svelte";
+    import Markdown from "$lib/components/Markdown.svelte";
+
+    let isLoading = $state<boolean>(false);
+    let article = $state<Article | null>(null);
+
+    onMount(async () => {
+        if (page.params.articleId) {
+            article = await bloggState.loadArticle(page.params.articleId);
+        }
+    });
+
+
+</script>
+
+{#if bloggState.isLoading}
+    <div class="loading">Laster artikler...</div>
+{/if}
+
+<div class="buttons">
+    <a href="/" class="back-button">Tilbake til forsiden...</a>
+</div>
+
+
+{#if article}
+    <article class="blog-post">
+        <h1>{article.title}</h1>
+        <div class="blog-time">{article.publishedDate}</div>
+        <div class="blog-content">
+            <Markdown toHtml={article.contents}></Markdown>
+        </div>
+    </article>
+{:else}
+    <p>Ingen artikkel funnet...</p>
+{/if}
+
+<style>
+    .blog-post {
+        max-width: 1000px;
+        margin: 0 auto;
+    }
+
+    .buttons {
+        max-width: 1000px;
+        margin: 25px auto;
+    }
+
+    .buttons .back-button {
+        margin-top: 55px;
+        padding: 10px 17px;
+        border: 1px solid #ddd;
+        background: aliceblue;
+        border-radius: 7px;
+    }
+</style>

Steg 5 - Legger til en egen app.css fil

/src/app.css
+a {
+    text-decoration: none;
+    color: inherit; /* Optional: to keep the link color consistent with surrounding text */
+}
+
+a:hover {
+    color: #007bff; /* Optional: change color on hover */
+}
+
+a:visited {
+    color: inherit; /* Optional: change color for visited links */
+}
/src/routes/+layout.svelte
 <script>
+    import '../app.css';
     import Header from "$lib/components/Header.svelte";
 </script>
 
@@ -7,16 +8,5 @@
 <slot></slot>
 
 <style>
-    :global(a) {
-        text-decoration: none;
-        color: inherit; /* Optional: to keep the link color consistent with surrounding text */
-    }
 
-    :global(a:hover) {
-        color: #007bff; /* Optional: change color on hover */
-    }
-
-    :global(a:visited) {
-        color: inherit; /* Optional: change color for visited links */
-    }
 </style>

Top categories

Loading Svelte Themes