diff --git a/package.json b/package.json
index 7e864292101..423f91860c1 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
         "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
         "lint": "prettier --plugin-search-dir . --check . && eslint .",
         "format": "prettier --plugin-search-dir . --write .",
+        "clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i",
         "test:integration": "playwright test",
         "test:unit": "vitest",
         "icons:build": "node ./src/icons/build.js",
diff --git a/src/app.d.ts b/src/app.d.ts
index f59b884c51e..7b32cc9ae18 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -1,12 +1,14 @@
 // See https://kit.svelte.dev/docs/types#app
 // for information about these interfaces
 declare global {
-	namespace App {
-		// interface Error {}
-		// interface Locals {}
-		// interface PageData {}
-		// interface Platform {}
-	}
+    namespace App {
+        // interface Error {}
+        // interface Locals {}
+        interface PageData {
+            changelogEntries: number;
+        }
+        // interface Platform {}
+    }
 }
 
 export {};
diff --git a/src/lib/animations/scroll-indicator.svelte b/src/lib/animations/scroll-indicator.svelte
index 9769f7b0601..71c10309781 100644
--- a/src/lib/animations/scroll-indicator.svelte
+++ b/src/lib/animations/scroll-indicator.svelte
@@ -1,82 +1,58 @@
 <script lang="ts">
-	import { rect } from '$lib/actions';
-	import { clamp } from '$lib/utils/clamp';
-	import { onMount } from 'svelte';
-	import { writable } from 'svelte/store';
-
-	export let percentage = 0;
-	let easedPercentage = 0;
-
-	const elRect = writable<DOMRect | null>(null);
-	$: y = $elRect ? clamp(0, easedPercentage, 1) * $elRect.height : 0;
-
-	onMount(() => {
-		let frame: number | null = null;
-		const ease = () => {
-			easedPercentage += (percentage - easedPercentage);
-			frame = window.requestAnimationFrame(ease);
-		};
-		ease();
-
-		return () => {
-			frame && window.cancelAnimationFrame(frame);
-		};
-	});
+    import { rect } from '$lib/actions';
+    import { clamp } from '$lib/utils/clamp';
+    import { onMount } from 'svelte';
+    import { writable } from 'svelte/store';
+
+    export let percentage = 0;
+    let easedPercentage = 0;
+
+    const elRect = writable<DOMRect | null>(null);
+    $: y = $elRect ? clamp(0, easedPercentage, 1) * $elRect.height : 0;
+
+    onMount(() => {
+        let frame: number | null = null;
+        const ease = () => {
+            easedPercentage += percentage - easedPercentage;
+            frame = window.requestAnimationFrame(ease);
+        };
+        ease();
+
+        return () => {
+            frame && window.cancelAnimationFrame(frame);
+        };
+    });
 </script>
 
 <div
-	class="scroll-indicator"
-	use:rect={elRect}
-	style:--y={`${y}px`}
-	style:--percentage={`${easedPercentage * 100}%`}
+    class="scroll-indicator"
+    use:rect={elRect}
+    style:--y={`${y}px`}
+    style:--percentage={`${easedPercentage * 100}%`}
 >
-	<div class="cursor" />
+    <div class="aw-dot" />
 </div>
 
 <style lang="scss">
-	.scroll-indicator {
-		position: relative;
-
-		width: 1px;
-		flex-shrink: 0;
-		height: 100%;
-		background: linear-gradient(
-			to bottom,
-			hsl(var(--aw-color-accent)) 0%,
-			hsl(var(--aw-color-greyscale-700)) var(--percentage),
-			hsl(var(--aw-color-greyscale-700)) 100%
-		);
-		border-radius: 100%;
-
-		.cursor {
-			border-radius: 16px;
-			border: 0.5px solid rgba(255, 255, 255, 0.16);
-			background: linear-gradient(
-				138deg,
-				rgba(255, 255, 255, 0.13) 9.61%,
-				rgba(255, 255, 255, 0) 105.41%
-			);
-			backdrop-filter: blur(100px);
-
-			width: 16px;
-			height: 16px;
-
-			position: absolute;
-			left: 50%;
-			translate: -50% var(--y, 0);
-			top: -8px;
-
-			&::after {
-				content: '';
-				background-color: white;
-				border-radius: 100%;
-				width: 4px;
-				height: 4px;
-				position: absolute;
-				left: 50%;
-				top: 50%;
-				translate: -50% -50%;
-			}
-		}
-	}
+    .scroll-indicator {
+        position: relative;
+
+        width: 1px;
+        flex-shrink: 0;
+        height: 100%;
+        background: linear-gradient(
+            to bottom,
+            hsl(var(--aw-color-accent)) 0%,
+            hsl(var(--aw-color-greyscale-700)) var(--percentage),
+            hsl(var(--aw-color-greyscale-700)) 100%
+        );
+        border-radius: 100%;
+    }
+
+    .aw-dot {
+        position: absolute;
+        left: 50%;
+        translate: -50% var(--y, 0);
+        top: -8px;
+    }
 </style>
diff --git a/src/lib/components/Article.svelte b/src/lib/components/Article.svelte
index 7b59e0bb526..94d662879c4 100644
--- a/src/lib/components/Article.svelte
+++ b/src/lib/components/Article.svelte
@@ -1,4 +1,6 @@
 <script lang="ts">
+    import { formatDate } from '$lib/utils/date';
+
     export let title: string;
     export let cover: string;
     export let href: string;
@@ -24,11 +26,7 @@
                         <h4 class="aw-sub-body-400 aw-u-color-text-primary">{author}</h4>
                         <ul class="aw-metadata aw-caption-400 aw-is-not-mobile">
                             <li>
-                                {Intl.DateTimeFormat('en-US', {
-                                    day: '2-digit',
-                                    month: 'short',
-                                    year: 'numeric'
-                                }).format(date)}
+                                {formatDate(date)}
                             </li>
                             <li>{timeToRead} min</li>
                         </ul>
diff --git a/src/lib/components/FooterNav.svelte b/src/lib/components/FooterNav.svelte
index 41d1f993271..17ced1ca857 100644
--- a/src/lib/components/FooterNav.svelte
+++ b/src/lib/components/FooterNav.svelte
@@ -68,7 +68,8 @@
                     rel: 'noopener noreferrer'
                 },
                 { label: 'Contact us', href: '/contact-us' },
-                { label: 'Assets', href: '/assets' }
+                { label: 'Assets', href: '/assets' },
+                { label: 'Changelog', href: '/changelog' }
             ],
             Policies: [
                 { label: 'Terms', href: '/terms' },
diff --git a/src/lib/layouts/Main.svelte b/src/lib/layouts/Main.svelte
index 1398aefb90a..865065ae32b 100644
--- a/src/lib/layouts/Main.svelte
+++ b/src/lib/layouts/Main.svelte
@@ -4,9 +4,12 @@
     export type NavLink = {
         label: string;
         href: string;
+        showBadge?: boolean;
     };
     export const isHeaderHidden = writable(false);
     export const isMobileNavOpen = writable(false);
+
+    const initialized = writable(false);
 </script>
 
 <script lang="ts">
@@ -15,8 +18,10 @@
     import { BANNER_KEY } from '$lib/constants';
     import { isVisible } from '$lib/utils/isVisible';
     import { createScrollInfo } from '$lib/utils/scroll';
+    import { hasNewChangelog } from '$routes/changelog/utils';
     import { addEventListener } from '@melt-ui/svelte/internal/helpers';
     import { onMount } from 'svelte';
+    import { page } from '$app/stores';
 
     export let omitMainId = false;
     let theme: 'light' | 'dark' | null = 'dark';
@@ -75,6 +80,9 @@
     }
 
     onMount(() => {
+        setTimeout(() => {
+            $initialized = true;
+        }, 1000);
         return setupThemeObserver();
     });
 
@@ -91,6 +99,11 @@
             label: 'Blog',
             href: '/blog'
         },
+        {
+            label: 'Changelog',
+            href: '/changelog',
+            showBadge: hasNewChangelog() && !$page.url.pathname.includes('/changelog')
+        },
         {
             label: 'Pricing',
             href: '/pricing'
@@ -204,9 +217,15 @@
                 </a>
                 <nav class="aw-main-header-nav" aria-label="Main">
                     <ul class="aw-main-header-nav-list">
-                        {#each navLinks as { label, href }}
+                        {#each navLinks as navLink}
                             <li class="aw-main-header-nav-item">
-                                <a class="aw-link" {href}>{label}</a>
+                                <a
+                                    class="aw-link"
+                                    href={navLink.href}
+                                    data-initialized={$initialized ? '' : undefined}
+                                    data-badge={navLink.showBadge ? '' : undefined}
+                                    >{navLink.label}
+                                </a>
                             </li>
                         {/each}
                     </ul>
@@ -242,3 +261,40 @@
         <slot />
     </main>
 </div>
+
+<style lang="scss">
+    .nav-badge {
+        margin-inline-start: 0.5rem;
+        padding-inline: 0.375rem;
+    }
+
+    @keyframes scale-in {
+        0% {
+            transform: scale(0);
+        }
+        100% {
+            transform: scale(1);
+        }
+    }
+
+    [data-badge] {
+        position: relative;
+
+        &::after {
+            content: '';
+            position: absolute;
+            background-color: hsl(var(--aw-color-accent));
+            border-radius: 100%;
+            width: 0.375rem;
+            height: 0.375rem;
+
+            inset-block-start: -2px;
+            inset-inline-end: -4px;
+            translate: 100%;
+        }
+
+        &:not([data-initialized])::after {
+            animation: scale-in 0.2s ease-out;
+        }
+    }
+</style>
diff --git a/src/lib/utils/date.ts b/src/lib/utils/date.ts
index 6e21ac83290..95084b2b2da 100644
--- a/src/lib/utils/date.ts
+++ b/src/lib/utils/date.ts
@@ -1,7 +1,7 @@
 export const formatDate = (date: string): string => {
-    const dt = new Date(date);
-    const month = dt.toLocaleString('default', { month: 'short' });
-    const day = dt.getDate();
-    const year = dt.getFullYear();
-    return `${month} ${day}, ${year}`;
+  const dt = new Date(date);
+  const month = dt.toLocaleString('default', { month: 'short' });
+  const day = dt.getDate();
+  const year = dt.getFullYear();
+  return `${month} ${day}, ${year}`;
 };
diff --git a/src/lib/utils/is.ts b/src/lib/utils/is.ts
new file mode 100644
index 00000000000..d0818fc229f
--- /dev/null
+++ b/src/lib/utils/is.ts
@@ -0,0 +1,9 @@
+export function isNumeric(value: unknown): boolean {
+    if (typeof value === 'number' && !isNaN(value)) {
+        return true;
+    }
+    if (typeof value === 'string' && value.trim() !== '') {
+        return !isNaN(Number(value));
+    }
+    return false;
+}
diff --git a/src/markdoc/layouts/Changelog.svelte b/src/markdoc/layouts/Changelog.svelte
new file mode 100644
index 00000000000..9fb803ab85c
--- /dev/null
+++ b/src/markdoc/layouts/Changelog.svelte
@@ -0,0 +1,27 @@
+<script lang="ts" context="module">
+    import { hasContext, setContext } from 'svelte';
+
+    export type ChangelogData = {
+        title: string;
+        description?: string;
+        date: string;
+        cover?: string;
+    };
+
+    const CONTEXT_KEY = Symbol('changelog');
+
+    function initContext() {
+        setContext(CONTEXT_KEY, null);
+    }
+
+    export function isInsideChangelog() {
+        return hasContext(CONTEXT_KEY);
+    }
+</script>
+
+<script lang="ts">
+    initContext();
+</script>
+
+<!-- Empty so it can be displayed inline -->
+<slot />
diff --git a/src/markdoc/nodes/Item.svelte b/src/markdoc/nodes/Item.svelte
index af93946a23d..7d32536f9c9 100644
--- a/src/markdoc/nodes/Item.svelte
+++ b/src/markdoc/nodes/Item.svelte
@@ -1,7 +1,9 @@
 <script lang="ts">
+    import { isInsideChangelog } from '$markdoc/layouts/Changelog.svelte';
     import { getContext } from 'svelte';
 
     const isDocs = getContext('isDocs') ?? false;
+    const inChangelog = isInsideChangelog();
 </script>
 
-<li><p class:aw-paragraph-lg={!isDocs}><slot /></p></li>
+<li><p class:aw-paragraph-lg={!isDocs && !inChangelog}><slot /></p></li>
diff --git a/src/markdoc/nodes/Link.svelte b/src/markdoc/nodes/Link.svelte
index 881f4b02fb4..8f9f1836130 100644
--- a/src/markdoc/nodes/Link.svelte
+++ b/src/markdoc/nodes/Link.svelte
@@ -1,4 +1,5 @@
 <script lang="ts">
+    import { isInsideChangelog } from '$markdoc/layouts/Changelog.svelte';
     import { getContext } from 'svelte';
     import { TABLE_CTX_KEY } from './Table.svelte';
 
@@ -10,13 +11,21 @@
     const rel = isExternal ? 'noopener nofollow' : undefined;
 
     const isDocs = getContext('isDocs') ?? false;
+    const inChangelog = isInsideChangelog();
     const inTable = getContext(TABLE_CTX_KEY) ?? false;
 </script>
 
 <a
-    class="aw-link is-inline"
+    class="aw-link is-inline {isDocs || inChangelog ? 'aw-paragraph-md' : 'aw-paragraph-lg'}"
+    data-in-changelog={inChangelog ? '' : undefined}
     {href}
     {title}
     {target}
     {rel}><slot /></a
->
\ No newline at end of file
+>
+
+<style lang="scss">
+    [data-in-changelog]:last-child {
+        margin-block-start: 1rem;
+    }
+</style>
diff --git a/src/markdoc/nodes/List.svelte b/src/markdoc/nodes/List.svelte
index ac322bcc40b..cbb77befa1e 100644
--- a/src/markdoc/nodes/List.svelte
+++ b/src/markdoc/nodes/List.svelte
@@ -1,4 +1,5 @@
 <script lang="ts">
+    import { isInsideChangelog } from '$markdoc/layouts/Changelog.svelte';
     import { isInsidePolicy } from '$markdoc/layouts/Policy.svelte';
     import { setContext } from 'svelte';
 
@@ -7,13 +8,15 @@
     setContext('no-paragraph', true);
 
     const inPolicy = isInsidePolicy();
+    const inChangelog = isInsideChangelog();
 </script>
 
 <svelte:element
     this={ordered ? 'ol' : 'ul'}
     class:aw-numeric-list={ordered}
     class:aw-pink-dots={!ordered}
-    class:in-policy={inPolicy}><slot /></svelte:element
+    class:in-policy={inPolicy}
+    class:in-changelog={inChangelog}><slot /></svelte:element
 >
 
 <style lang="scss">
@@ -42,4 +45,10 @@
             padding-inline-end: 0;
         }
     }
+
+    .in-changelog {
+        padding-inline-start: 0.9rem;
+        margin-block-end: 1rem;
+        gap: 0.75rem;
+    }
 </style>
diff --git a/src/markdoc/nodes/Paragraph.svelte b/src/markdoc/nodes/Paragraph.svelte
index ec9913fec0c..1b66c8fc3d4 100644
--- a/src/markdoc/nodes/Paragraph.svelte
+++ b/src/markdoc/nodes/Paragraph.svelte
@@ -1,10 +1,12 @@
 <script lang="ts">
+    import { isInsideChangelog } from '$markdoc/layouts/Changelog.svelte';
     import { isInsidePolicy } from '$markdoc/layouts/Policy.svelte';
     import { getContext, hasContext } from 'svelte';
 
     const noParagraph = hasContext('no-paragraph') ? getContext('no-paragraph') : false;
     const isDocs = getContext('isDocs') ?? false;
     const inPolicy = isInsidePolicy();
+    const inChangelog = isInsideChangelog();
 
     let className = '';
     export { className as class };
@@ -16,6 +18,8 @@
     <p class="aw-paragraph-md {className}"><slot /></p>
 {:else if inPolicy}
     <p class="aw-paragraph-md in-policy {className}"><slot /></p>
+{:else if inChangelog}
+    <p class="aw-paragraph-lg in-changelog {className}"><slot /></p>
 {:else}
     <p class="aw-paragraph-lg {className}"><slot /></p>
 {/if}
@@ -28,4 +32,9 @@
     .in-policy {
         margin-block-end: 1rem;
     }
+
+    .in-changelog {
+        margin-block-end: 1rem !important;
+        font-weight: 400;
+    }
 </style>
diff --git a/src/markdoc/tags/Arrow_Link.svelte b/src/markdoc/tags/Arrow_Link.svelte
index f56dca1c6e2..fa3c29d9ff3 100644
--- a/src/markdoc/tags/Arrow_Link.svelte
+++ b/src/markdoc/tags/Arrow_Link.svelte
@@ -1,4 +1,5 @@
 <script lang="ts">
+    import { isInsideChangelog } from '$markdoc/layouts/Changelog.svelte';
     import { getContext } from 'svelte';
 
     export let href: string;
@@ -6,16 +7,25 @@
     const isExternal = ['http://', 'https://'].some((prefix) => href.startsWith(prefix));
     const target = isExternal ? '_blank' : undefined;
     const rel = isExternal ? 'noopener nofollow' : undefined;
+    const inChangelog = isInsideChangelog();
 
     const isDocs = getContext('isDocs') ?? false;
 </script>
 
-<a class="aw-link {isDocs ? 'aw-paragraph-md' : 'aw-paragraph-lg'}" {href} {target} {rel}
-    ><slot /><span class="icon-cheveron-right" style:font-size="16px" /></a
+<a
+    class="aw-link {isDocs ? 'aw-paragraph-md' : 'aw-paragraph-lg'}"
+    data-in-changelog={inChangelog ? '' : undefined}
+    {href}
+    {target}
+    {rel}><slot /><span class="icon-cheveron-right" style:font-size="16px" /></a
 >
 
 <style>
     a {
         display: flex;
     }
+
+    [data-in-changelog]:last-child {
+        padding-block-start: 1rem;
+    }
 </style>
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts
index 189f71e2e1b..a8038489f96 100644
--- a/src/routes/+layout.server.ts
+++ b/src/routes/+layout.server.ts
@@ -1 +1,11 @@
+import { getAllChangelogEntries } from "./changelog/utils";
+
 export const prerender = true;
+
+
+export const load = async () => {
+  return {
+    changelogEntries: (await getAllChangelogEntries()).length
+  }
+
+};
\ No newline at end of file
diff --git a/src/routes/changelog/(entries)/2023-08-30.markdoc b/src/routes/changelog/(entries)/2023-08-30.markdoc
new file mode 100644
index 00000000000..c9bab016155
--- /dev/null
+++ b/src/routes/changelog/(entries)/2023-08-30.markdoc
@@ -0,0 +1,18 @@
+---
+layout: changelog
+title: Appwrite 1.4
+date: 2023-08-30
+cover: /images/changelog/2023-08-30.png
+---
+
+We’ve just released Appwrite 1.4 with the goals to eliminate barriers to get started, maximize flexibility to grow, and deliver improved security for peace of mind.
+
+Notable new features: 
+- Next generation of Appwrite Functions brings Git deployments, http executions, intuitive syntax, and reliable logging.
+- Command center for quicker console navigation and keyboard shortcuts
+- AI assistant to help you search docs and answer questions.
+- One-Click migrations lets you migrate from Firebase, Supabase, and NHost. You can also migrate between Appwrite Cloud and self-hosted.
+
+{% arrow_link href="https://dev.to/appwrite/appwrite-14-brings-command-center-ai-one-click-migrations-and-upgraded-functions-1ch2" %}
+Read the full announcement
+{% /arrow_link %}
diff --git a/src/routes/changelog/(entries)/2023-09-21.markdoc b/src/routes/changelog/(entries)/2023-09-21.markdoc
new file mode 100644
index 00000000000..c04f1805aaa
--- /dev/null
+++ b/src/routes/changelog/(entries)/2023-09-21.markdoc
@@ -0,0 +1,16 @@
+---
+layout: changelog
+title: Meet the new Appwrite
+date: 2023-09-21
+cover: /images/changelog/2023-09-21.png
+---
+
+Unveiling Appwrite's new fresh look and feel with a new brand, website and experience.
+
+- New brand, new logo, and new design that aligns with our growth
+- Design elements inspired by the community and reflects our values
+- New documentation with improved navigation, search, and more beginner friendly tutorials
+
+{% arrow_link href="/changelog/2023-12-20" %}
+Read the full announcement
+{% /arrow_link %}
diff --git a/src/routes/changelog/(entries)/2023-12-20.markdoc b/src/routes/changelog/(entries)/2023-12-20.markdoc
new file mode 100644
index 00000000000..f5f835bab6f
--- /dev/null
+++ b/src/routes/changelog/(entries)/2023-12-20.markdoc
@@ -0,0 +1,19 @@
+---
+layout: changelog
+title: Announcing Appwrite Pro
+date: 2023-12-20
+cover: /images/changelog/2023-12-20.png
+---
+
+Appwrite Pro is now available for Appwrite Cloud developers.
+
+Previously, all Appwrite Cloud developers used the Starter plan with unrestricted resources.
+With Pro, you can choose between two different plans for your organization(s):
+
+- Starter: a free plan that provides an easy starting point for budding projects
+- Pro: for developers who need more resources to scale their apps, 
+customized branding in emails and messages, more team members per organization, and premium support.
+
+{% arrow_link href="/blog/announcing-appwrite-pro" %}
+Read the full announcement
+{% /arrow_link %}
diff --git a/src/routes/changelog/(entries)/2023-12-23.markdoc b/src/routes/changelog/(entries)/2023-12-23.markdoc
new file mode 100644
index 00000000000..ed16b0c0432
--- /dev/null
+++ b/src/routes/changelog/(entries)/2023-12-23.markdoc
@@ -0,0 +1,11 @@
+---
+layout: changelog
+title: Changelog
+date: 2023-12-23
+cover: /images/changelog/2023-12-23.png
+---
+
+Appwrite's capabilities grow everyday and it's difficult to keep up.
+To help everyone stay updated, we added a dedicated changelog page to the Appwrite website. 
+
+You can now find all of Appwrite's latest features, changes, and announcements all in one place.
\ No newline at end of file
diff --git a/src/routes/changelog/ChangelogEntry.svelte b/src/routes/changelog/ChangelogEntry.svelte
new file mode 100644
index 00000000000..b07337f75fd
--- /dev/null
+++ b/src/routes/changelog/ChangelogEntry.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+    import { formatDate } from '$lib/utils/date';
+    import type { PageData } from './[[page]]/$types';
+
+    export let entry: PageData['entries'][0];
+</script>
+
+<div class="changelog-entry">
+    <time class="aw-caption-400 padded" datetime={entry.date}>{formatDate(entry.date)}</time>
+    {#if entry.cover}
+        <a href={entry.href} class="aw-media">
+            <img src={entry.cover} alt="" class="aw-u-media-ratio-16-9 u-width-full-line" />
+        </a>
+    {/if}
+
+    <div class="padded">
+        <h2 class="aw-title aw-u-color-text-primary">
+            <a href={entry.href}>
+                {entry.title}
+            </a>
+        </h2>
+        <slot />
+    </div>
+</div>
+
+<style lang="scss">
+    .changelog-entry {
+        display: grid;
+        gap: 20px;
+    }
+
+    h2 {
+        display: grid;
+        padding-block: 0.5rem 1rem;
+
+        &:hover {
+            text-decoration: underline;
+        }
+    }
+
+    @media screen and (min-width: 513px) {
+        .padded {
+            padding-inline: 1rem;
+        }
+    }
+</style>
diff --git a/src/routes/changelog/[[page]]/+page.svelte b/src/routes/changelog/[[page]]/+page.svelte
new file mode 100644
index 00000000000..4adf46cd0ae
--- /dev/null
+++ b/src/routes/changelog/[[page]]/+page.svelte
@@ -0,0 +1,140 @@
+<script lang="ts">
+    import { goto } from '$app/navigation';
+    import { FooterNav, MainFooter, PreFooter } from '$lib/components';
+    import { Main } from '$lib/layouts';
+    import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
+    import { onMount } from 'svelte';
+    import ChangelogEntry from '../ChangelogEntry.svelte';
+    import { page } from '$app/stores';
+    import { CHANGELOG_KEY } from '../utils';
+
+    export let data;
+
+    const seo = {
+        title: 'Changelog',
+        description: DEFAULT_DESCRIPTION,
+        ogImage: `${DEFAULT_HOST}/images/open-graph/website.png`
+    };
+
+    function loadMore() {
+        goto(`/changelog/${data.nextPage}`, { replaceState: true, noScroll: true });
+    }
+
+    onMount(() => {
+        localStorage.setItem(CHANGELOG_KEY, $page.data.changelogEntries.toString());
+    });
+</script>
+
+<svelte:head>
+    <!-- Titles -->
+    <title>{seo.title}</title>
+    <meta property="og:title" content={seo.title} />
+    <meta name="twitter:title" content={seo.title} />
+    <!-- Desscription -->
+    <meta name="description" content={seo.description} />
+    <meta property="og:description" content={seo.description} />
+    <meta name="twitter:description" content={seo.description} />
+    <!-- Image -->
+    <meta property="og:image" content={seo.ogImage} />
+    <meta property="og:image:width" content="1200" />
+    <meta property="og:image:height" content="630" />
+    <meta name="twitter:image" content={seo.ogImage} />
+    <meta name="twitter:card" content="summary_large_image" />
+</svelte:head>
+
+<Main>
+    <div class="wrapper">
+        <h1 class="aw-display aw-u-color-text-primary">Changelog</h1>
+        <ul>
+            {#each data.entries as entry}
+                <li>
+                    <div class="aw-dot" />
+                    <ChangelogEntry {entry}>
+                        <svelte:component this={entry.component} />
+                    </ChangelogEntry>
+                </li>
+            {/each}
+        </ul>
+
+        {#if data.nextPage}
+            <button class="aw-button is-secondary" on:click={loadMore}>Load more</button>
+        {/if}
+    </div>
+    <div
+        class="aw-big-padding-section-level-2 is-margin-replace-padding u-position-relative u-overflow-hidden"
+    >
+        <div class="aw-container">
+            <PreFooter />
+            <FooterNav />
+            <MainFooter />
+        </div>
+    </div>
+</Main>
+
+<style lang="scss">
+    .wrapper {
+        --padding-inline: 1rem;
+
+        max-width: calc(49.375rem + var(--padding-inline) * 2);
+        margin-block-start: 7.5rem;
+        margin-inline: auto;
+        padding-inline: var(--padding-inline);
+        padding-block-end: 10rem;
+    }
+
+    ul {
+        --padding-is: 2rem;
+        --dot-offset: 0.25rem;
+
+        display: grid;
+        gap: 5rem;
+
+        padding-inline-start: var(--padding-is);
+        border-inline-start: 2px solid transparent;
+        margin-block-start: 5rem;
+        position: relative;
+
+        &::before {
+            content: '';
+            background: linear-gradient(
+                to bottom,
+                hsl(var(--aw-color-greyscale-700)) 0%,
+                hsl(var(--aw-color-greyscale-700)) 95%,
+                transparent 100%
+            );
+
+            position: absolute;
+            inset-inline-start: -0.5px;
+            inset-block-start: var(--dot-offset);
+            width: 1px;
+            height: 100%;
+        }
+
+        li {
+            position: relative;
+
+            .aw-dot {
+                position: absolute;
+                inset-inline-start: calc(var(--padding-is) * -1);
+                translate: -50% var(--dot-offset);
+            }
+        }
+    }
+
+    button {
+        margin-block-start: 5rem;
+        margin-inline: auto;
+        min-inline-size: 10.9375rem;
+    }
+
+    @media screen and (max-width: 512px) {
+        ul {
+            padding-inline-start: 0;
+            margin-block-start: 2.5rem;
+
+            &::before {
+                display: none;
+            }
+        }
+    }
+</style>
diff --git a/src/routes/changelog/[[page]]/+page.ts b/src/routes/changelog/[[page]]/+page.ts
new file mode 100644
index 00000000000..30dddd4935d
--- /dev/null
+++ b/src/routes/changelog/[[page]]/+page.ts
@@ -0,0 +1,25 @@
+import { CHANGELOG_DEPENDENCY, getAllChangelogEntries } from '../utils';
+
+const PER_PAGE = 5;
+
+export const entries = async () => {
+  const entries = await getAllChangelogEntries();
+  const totalPages = Math.ceil(entries.length / PER_PAGE);
+  return Array.from({ length: totalPages }, (_, i) => {
+    return {
+      page: (i + 1).toString()
+    };
+  });
+};
+
+export const load = async ({ depends, params }) => {
+  depends(CHANGELOG_DEPENDENCY);
+
+  const page = parseInt(params.page || '1', 10);
+  const entries = await getAllChangelogEntries();
+
+  return {
+    entries: entries.slice(0, page * PER_PAGE),
+    nextPage: page < Math.ceil(entries.length / PER_PAGE) ? page + 1 : null
+  };
+};
diff --git a/src/routes/changelog/entry/[entry]/+page.svelte b/src/routes/changelog/entry/[entry]/+page.svelte
new file mode 100644
index 00000000000..07b4feed409
--- /dev/null
+++ b/src/routes/changelog/entry/[entry]/+page.svelte
@@ -0,0 +1,87 @@
+<script lang="ts">
+    import { FooterNav, MainFooter } from '$lib/components';
+    import PreFooter from '$lib/components/PreFooter.svelte';
+    import { Main } from '$lib/layouts';
+    import { formatDate } from '$lib/utils/date';
+    import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
+    import { CHANGELOG_TITLE_SUFFIX } from '$routes/titles';
+
+    export let data;
+
+    const seo = {
+        title: data.title,
+        description: data.description ?? DEFAULT_DESCRIPTION,
+        ogImage: data.cover
+            ? DEFAULT_HOST + data.cover
+            : `${DEFAULT_HOST}/images/open-graph/website.png`
+    };
+</script>
+
+<svelte:head>
+    <!-- Titles -->
+    <title>{seo.title + CHANGELOG_TITLE_SUFFIX}</title>
+    <meta property="og:title" content={seo.title} />
+    <meta name="twitter:title" content={seo.title} />
+    <!-- Desscription -->
+    <meta name="description" content={seo.description} />
+    <meta property="og:description" content={seo.description} />
+    <meta name="twitter:description" content={seo.description} />
+    <!-- Image -->
+
+    <meta property="og:image" content={seo.ogImage} />
+    <meta property="og:image:width" content="1200" />
+    <meta property="og:image:height" content="630" />
+    <meta name="twitter:image" content={seo.ogImage} />
+    <meta name="twitter:card" content="summary_large_image" />
+</svelte:head>
+
+<Main>
+    <div class="aw-container wrapper" style="--container-size:42.5rem">
+        <article class="aw-main-article">
+            <header class="aw-main-article-header">
+                <a class="aw-link aw-u-color-text-secondary" href="/changelog">
+                    <span class="aw-icon-chevron-left" aria-hidden="true" />
+                    <span>Back to Changelog</span>
+                </a>
+                <ul class="aw-metadata aw-caption-400">
+                    <li>
+                        <time datetime={data.date}>{formatDate(data.date)}</time>
+                    </li>
+                </ul>
+                <h1 class="aw-title aw-u-color-text-primary">{data.title}</h1>
+            </header>
+            {#if data.cover}
+                <div class="aw-media-container">
+                    <img class="u-block" src={data.cover} alt="" />
+                </div>
+            {/if}
+
+            <div class="aw-article-content u-margin-block-start-32">
+                <svelte:component this={data.component} />
+            </div>
+        </article>
+    </div>
+
+    <div
+        class="aw-big-padding-section-level-2 is-margin-replace-padding u-position-relative u-overflow-hidden"
+    >
+        <div class="aw-container">
+            <PreFooter />
+            <FooterNav />
+            <MainFooter />
+        </div>
+    </div>
+</Main>
+
+<style lang="scss">
+    .wrapper {
+        padding-block-start: 3rem;
+        padding-block-end: 10rem;
+    }
+
+    header a {
+        display: flex;
+        align-items: center;
+        translate: -0.35rem -0.25rem;
+    }
+</style>
diff --git a/src/routes/changelog/entry/[entry]/+page.ts b/src/routes/changelog/entry/[entry]/+page.ts
new file mode 100644
index 00000000000..3f348c40bb4
--- /dev/null
+++ b/src/routes/changelog/entry/[entry]/+page.ts
@@ -0,0 +1,28 @@
+import { error } from '@sveltejs/kit';
+import { getAllChangelogEntries } from '../../utils.js';
+import type { EntryGenerator } from './$types.js';
+
+export const entries: EntryGenerator = async () => {
+  const entries = await getAllChangelogEntries();
+  return entries.map((entry) => {
+    return {
+      entry: entry.slug
+    }
+  });
+}
+
+export const load = async ({ params, }) => {
+  const entries = await getAllChangelogEntries();
+  const entry = entries.find((entry) => {
+    return entry.filepath.includes(params.entry);
+  });
+
+  if (!entry) {
+    throw error(404, 'Not found')
+  }
+
+  return {
+    ...entry
+  }
+
+};
\ No newline at end of file
diff --git a/src/routes/changelog/utils.ts b/src/routes/changelog/utils.ts
new file mode 100644
index 00000000000..b313f22e791
--- /dev/null
+++ b/src/routes/changelog/utils.ts
@@ -0,0 +1,44 @@
+import { browser } from '$app/environment';
+import { page } from '$app/stores';
+import { isNumeric } from '$lib/utils/is';
+import type { ChangelogData } from '$markdoc/layouts/Changelog.svelte';
+import { get } from 'svelte/store';
+
+type ChangelogComponent = {
+    frontmatter: ChangelogData;
+    default: ConstructorOfATypedSvelteComponent;
+};
+
+export const getAllChangelogEntries = async () => {
+    const entriesGlob = await import.meta.glob('./**/*.markdoc', { eager: true });
+    let entries = Object.entries(entriesGlob).map(([filepath, component]) => {
+        const typedComponent = component as ChangelogComponent;
+        const { frontmatter } = typedComponent;
+        const slug = filepath.replace(/\.markdoc$/, '').replace('(entries)/', '');
+        const href = `/changelog/entry/${slug}`;
+
+        return { ...frontmatter, component: typedComponent.default, filepath, href, slug };
+    });
+    entries = entries.filter(({ filepath }) => {
+        return filepath.includes('(entries)');
+    });
+
+    return entries.sort((a, b) => {
+        const aDate = new Date(a.date);
+        const bDate = new Date(b.date);
+        return bDate.getTime() - aDate.getTime();
+    });
+};
+
+export const CHANGELOG_DEPENDENCY = 'changelog';
+
+export const CHANGELOG_KEY = 'changelog';
+
+export function hasNewChangelog() {
+    if (!browser) return false;
+
+    const prev = localStorage.getItem(CHANGELOG_KEY);
+    if (!prev) return true;
+
+    return isNumeric(prev) && JSON.parse(prev) < get(page).data.changelogEntries;
+}
diff --git a/src/routes/titles.ts b/src/routes/titles.ts
index 66920f6ba9b..71b4af2a05d 100644
--- a/src/routes/titles.ts
+++ b/src/routes/titles.ts
@@ -2,3 +2,4 @@ export const TITLE_SUFFIX = ' - Appwrite';
 export const DOCS_TITLE_SUFFIX = ' - Docs' + TITLE_SUFFIX;
 export const API_REFERENCE_TITLE_SUFFIX = ' API Reference' + DOCS_TITLE_SUFFIX;
 export const BLOG_TITLE_SUFFIX = ' - Blog' + TITLE_SUFFIX;
+export const CHANGELOG_TITLE_SUFFIX = ' - Changelog' + TITLE_SUFFIX;
diff --git a/src/scss/6-elements/_dot.scss b/src/scss/6-elements/_dot.scss
new file mode 100644
index 00000000000..2ac355ddffd
--- /dev/null
+++ b/src/scss/6-elements/_dot.scss
@@ -0,0 +1,29 @@
+@use '../abstract' as *;
+
+
+.#{$p}-dot {
+  border-radius: 16px;
+  border: 0.5px solid rgba(255, 255, 255, 0.16);
+  background: linear-gradient(
+      138deg,
+      rgba(255, 255, 255, 0.13) 9.61%,
+      rgba(255, 255, 255, 0) 105.41%
+  );
+  backdrop-filter: blur(100px);
+
+  width: 16px;
+  height: 16px;
+
+  
+  &::after {
+      content: '';
+      background-color: white;
+      border-radius: 100%;
+      width: 4px;
+      height: 4px;
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      translate: -50% -50%;
+  }
+}
\ No newline at end of file
diff --git a/src/scss/6-elements/_index.scss b/src/scss/6-elements/_index.scss
index 5dc3af6054d..12dc5ecf031 100644
--- a/src/scss/6-elements/_index.scss
+++ b/src/scss/6-elements/_index.scss
@@ -1,5 +1,6 @@
 @forward "text-tokens"; /* folder */
 
+@forward "dot";
 @forward "container";
 @forward "white-section";
 @forward "link";
diff --git a/src/scss/8-sequences/_main-article.scss b/src/scss/8-sequences/_main-article.scss
index 33a9606e8d4..fdc85a67ee7 100644
--- a/src/scss/8-sequences/_main-article.scss
+++ b/src/scss/8-sequences/_main-article.scss
@@ -4,13 +4,14 @@
   &-header {
     position:relative;
     display:flex; flex-direction:column; gap:pxToRem(8);
-    margin-block-end:pxToRem(48);
+    margin-block-end:pxToRem(32);
     @media #{$break2open} {
       padding-block-end:pxToRem(32); border-block-end:solid pxToRem(1) hsl(var(--aw-color-border));
     }
     @media #{$break3open} {
       .#{$p}-link {
-        position:absolute; inset-inline-end:calc(100% + #{pxToRem(74)});
+        position:absolute; 
+        inset-inline-end:calc(100% + #{pxToRem(74)});
         white-space:nowrap;
       }
     }
diff --git a/static/images/changelog/2023-08-30.png b/static/images/changelog/2023-08-30.png
new file mode 100644
index 00000000000..28b219e56c1
Binary files /dev/null and b/static/images/changelog/2023-08-30.png differ
diff --git a/static/images/changelog/2023-09-21.png b/static/images/changelog/2023-09-21.png
new file mode 100644
index 00000000000..ecc5710e433
Binary files /dev/null and b/static/images/changelog/2023-09-21.png differ
diff --git a/static/images/changelog/2023-12-20.png b/static/images/changelog/2023-12-20.png
new file mode 100644
index 00000000000..9c9aeff1ebd
Binary files /dev/null and b/static/images/changelog/2023-12-20.png differ
diff --git a/static/images/changelog/2023-12-23.png b/static/images/changelog/2023-12-23.png
new file mode 100644
index 00000000000..c562024d93c
Binary files /dev/null and b/static/images/changelog/2023-12-23.png differ
diff --git a/svelte.config.js b/svelte.config.js
index cd1059b4a02..8316dd0e454 100644
--- a/svelte.config.js
+++ b/svelte.config.js
@@ -7,7 +7,7 @@ import sequence from 'svelte-sequential-preprocessor';
 import staticAdapter from '@sveltejs/adapter-static';
 import nodeAdapter from '@sveltejs/adapter-node';
 
-function absoulute(path) {
+function absolute(path) {
     return join(dirname(fileURLToPath(import.meta.url)), path);
 }
 
@@ -23,17 +23,18 @@ const config = {
         vitePreprocess(),
         markdoc({
             generateSchema: true,
-            nodes: absoulute('./src/markdoc/nodes/_Module.svelte'),
-            tags: absoulute('./src/markdoc/tags/_Module.svelte'),
-            partials: absoulute('./src/partials'),
+            nodes: absolute('./src/markdoc/nodes/_Module.svelte'),
+            tags: absolute('./src/markdoc/tags/_Module.svelte'),
+            partials: absolute('./src/partials'),
             layouts: {
-                default: absoulute('./src/markdoc/layouts/Article.svelte'),
-                article: absoulute('./src/markdoc/layouts/Article.svelte'),
-                tutorial: absoulute('./src/markdoc/layouts/Tutorial.svelte'),
-                post: absoulute('./src/markdoc/layouts/Post.svelte'),
-                author: absoulute('./src/markdoc/layouts/Author.svelte'),
-                category: absoulute('./src/markdoc/layouts/Category.svelte'),
-                policy: absoulute('./src/markdoc/layouts/Policy.svelte')
+                default: absolute('./src/markdoc/layouts/Article.svelte'),
+                article: absolute('./src/markdoc/layouts/Article.svelte'),
+                tutorial: absolute('./src/markdoc/layouts/Tutorial.svelte'),
+                post: absolute('./src/markdoc/layouts/Post.svelte'),
+                author: absolute('./src/markdoc/layouts/Author.svelte'),
+                category: absolute('./src/markdoc/layouts/Category.svelte'),
+                policy: absolute('./src/markdoc/layouts/Policy.svelte'),
+                changelog: absolute('./src/markdoc/layouts/Changelog.svelte')
             }
         }),
         preprocessMeltUI()
@@ -52,6 +53,9 @@ const config = {
             $icons: './src/icons',
             $appwrite: './node_modules/@appwrite.io/repo',
             $markdoc: './src/markdoc'
+        },
+        prerender: {
+            handleHttpError: 'warn'
         }
     }
 };
