diff --git a/src/components/BucketListItem.vue b/src/components/BucketListItem.vue
index 176cddc2cc9d48350316395fd5b77992019c9d0b..dc5c26335737b3545f92b15e7eefc3007da2fb80 100644
--- a/src/components/BucketListItem.vue
+++ b/src/components/BucketListItem.vue
@@ -34,7 +34,7 @@ const emit = defineEmits<{
         :aria-current="props.active"
         :to="{
           name: 'bucket',
-          params: { bucket_name: bucket.name, sub_folders: [] },
+          params: { bucketName: bucket.name, subFolders: [] },
         }"
       >
         {{ bucket.name }}
diff --git a/src/components/BucketView.vue b/src/components/BucketView.vue
index eeb94f5c115582f64dca9a4f58b01fe67781797a..d45d20f17c1f405f75e3110fb726fdbcc8ef70f9 100644
--- a/src/components/BucketView.vue
+++ b/src/components/BucketView.vue
@@ -1,104 +1,303 @@
 <script setup lang="ts">
 import { useRoute } from "vue-router";
 import { onMounted, reactive, watch, computed } from "vue";
+import type { ComputedRef } from "vue";
 import type { S3ObjectMetaInformation } from "@/client";
 import { ObjectService } from "@/client";
 import BootstrapIcon from "@/components/BootstrapIcon.vue";
 import fileSize from "filesize";
 import dayjs from "dayjs";
 
+// Constants
+// -----------------------------------------------------------------------------
 const route = useRoute();
 
+// Typescript types
+// -----------------------------------------------------------------------------
+interface S3ObjectWithFolder extends S3ObjectMetaInformation {
+  folder: string[];
+  pseudoFileName: string;
+}
+
+type S3PseudoFolder = {
+  size: number;
+  parentFolder: string[];
+  last_modified: string;
+  name: string;
+  key: string;
+};
+
+type FolderTree = {
+  subFolders: Record<string, FolderTree>;
+  files: S3ObjectWithFolder[];
+};
+
+// Reactive State
+// -----------------------------------------------------------------------------
 const objectState = reactive({
   objects: [],
+  visibleObjects: [],
   loading: true,
-  bucket_not_found_error: false,
-  bucket_permission_error: false,
+  bucketNotFoundError: false,
+  bucketPermissionError: false,
 } as {
   objects: S3ObjectMetaInformation[];
+  visibleObjects: (S3ObjectWithFolder | S3PseudoFolder)[];
   loading: boolean;
-  bucket_not_found_error: boolean;
-  bucket_permission_error: boolean;
+  bucketNotFoundError: boolean;
+  bucketPermissionError: boolean;
 });
 
+// Watcher
+// -----------------------------------------------------------------------------
 watch(
   () => route.params,
   (newRouteParams, oldRouteParams) => {
     if (
-      newRouteParams.bucket_name &&
-      oldRouteParams.bucket_name !== newRouteParams.bucket_name
+      newRouteParams.bucketName &&
+      oldRouteParams.bucketName !== newRouteParams.bucketName
+    ) {
+      // If bucket is changed, update the objects
+      updateObjects(newRouteParams.bucketName as string);
+    } else if (
+      newRouteParams.subFolders &&
+      oldRouteParams.subFolders !== newRouteParams.subFolders
     ) {
-      update_objects(newRouteParams.bucket_name as string);
+      // If sub folder is changed, update the visible objects
+      updateVisibleObjects(newRouteParams.subFolders as string[]);
     }
   }
 );
 
-const sub_folder_in_url = computed(() => route.params.sub_folders.length > 0);
-const error_loading_objects = computed(
-  () =>
-    objectState.bucket_permission_error || objectState.bucket_not_found_error
+watch(
+  () => objectState.objects,
+  () => {
+    updateVisibleObjects(
+      subFolderInUrl.value ? (route.params.subFolders as string[]) : []
+    );
+  }
 );
 
+// Computed Properties
+// -----------------------------------------------------------------------------
+const folderStructure: ComputedRef<FolderTree> = computed(() => {
+  /**
+   * Store the entire folder structure in a bucket in a tree-like data structure
+   */
+  return objectsWithFolders.value.reduce(
+    // Add one object after another to the folder structure
+    (fTree, currentObject) => {
+      // If the object is not in a sub folder, but it in the top level 'folder'
+      if (currentObject.folder.length === 0) {
+        fTree.files.push(currentObject);
+      } else {
+        // If the object is in a sub folder
+        let currentFolder: FolderTree = fTree;
+        // For every sub folder the object is in , navigate into the sub folder
+        for (const folderName of currentObject.folder) {
+          // If the sub folder doesn't exist yet, create it
+          if (
+            Object.keys(currentFolder.subFolders).find(
+              (subFolderName) => subFolderName === folderName
+            ) == undefined
+          ) {
+            currentFolder.subFolders[folderName] = {
+              subFolders: {},
+              files: [],
+            };
+          }
+          // navigate into the sub folder
+          currentFolder = currentFolder.subFolders[folderName] as FolderTree;
+        }
+        // Add object to the folder
+        currentFolder.files.push(currentObject);
+      }
+      return fTree;
+    },
+    // Empty folder structure as initial value
+    {
+      subFolders: {},
+      files: [],
+    } as FolderTree
+  );
+});
+
+const objectsWithFolders: ComputedRef<S3ObjectWithFolder[]> = computed(() => {
+  /**
+   * Add to the meta information from objects the pseudo filename and their pseudo folder
+   * This can be inferred from the key of the object where the '/' character is the delimiter, e.g.
+   * dir1/dir2/text.txt ->
+   *  folder: dir1, dir2
+   *  filename: text.txt
+   */
+  return objectState.objects.map((obj) => {
+    const splittedKey = obj.key.split("/");
+    return {
+      ...obj,
+      pseudoFileName: splittedKey[splittedKey.length - 1],
+      folder: splittedKey.slice(0, splittedKey.length - 1),
+    };
+  });
+});
+
+const subFolderInUrl: ComputedRef<boolean> = computed(
+  () => route.params.subFolders != null && route.params.subFolders.length > 0
+);
+const errorLoadingObjects: ComputedRef<boolean> = computed(
+  () => objectState.bucketPermissionError || objectState.bucketNotFoundError
+);
+
+// Lifecycle Hooks
+// -----------------------------------------------------------------------------
 onMounted(() => {
-  update_objects(route.params.bucket_name as string);
+  updateObjects(route.params.bucketName as string);
 });
 
-function update_objects(bucket_name: string) {
-  objectState.bucket_not_found_error = false;
-  objectState.bucket_permission_error = false;
+// Functions
+// -----------------------------------------------------------------------------
+/**
+ * Calculate recursively the cumulative file size of all o objects in a folder
+ * @param folder Folder to inspect
+ * @returns The size of this folder in bytes
+ */
+function calculateFolderSize(folder: FolderTree): number {
+  let folderSize = 0;
+  folderSize += folder.files.reduce((acc, file) => acc + file.size, 0);
+  for (const subFolderName of Object.keys(folder.subFolders)) {
+    folderSize += calculateFolderSize(folder.subFolders[subFolderName]);
+  }
+  return folderSize;
+}
+
+/**
+ * Calculate recursively when an object in a folder were modified the last time
+ * @param folder Folder to inspect
+ * @returns The last modified timestamp as ISO string
+ */
+function calculateFolderLastModified(folder: FolderTree): string {
+  let lastModified: dayjs.Dayjs;
+  lastModified = folder.files
+    .map((f) => dayjs(f.last_modified))
+    .reduce(
+      (acc, fileAccessed) => (fileAccessed.isAfter(acc) ? fileAccessed : acc),
+      dayjs("2000-01-01")
+    );
+  for (const subFolderName of Object.keys(folder.subFolders)) {
+    const lastModifiedSubFolder = dayjs(
+      calculateFolderLastModified(folder.subFolders[subFolderName])
+    );
+    if (lastModifiedSubFolder.isAfter(lastModified)) {
+      lastModified = lastModifiedSubFolder;
+    }
+  }
+  return lastModified.toISOString();
+}
+
+/**
+ * Load the meta information about objects from a bucket
+ * @param bucketName Name of a bucket
+ */
+function updateObjects(bucketName: string) {
+  objectState.bucketNotFoundError = false;
+  objectState.bucketPermissionError = false;
   objectState.loading = true;
-  ObjectService.objectGetBucketObjects(bucket_name)
+  ObjectService.objectGetBucketObjects(bucketName)
     .then((objs) => {
       objectState.objects = objs;
     })
     .catch((error) => {
       if (error.status === 404) {
-        objectState.bucket_not_found_error = true;
+        objectState.bucketNotFoundError = true;
       } else if (error.status == 403) {
-        objectState.bucket_permission_error = true;
+        objectState.bucketPermissionError = true;
       }
     })
     .finally(() => {
       objectState.loading = false;
     });
 }
+
+/**
+ * Update the visible objects based on the current sub folder
+ * @param subFolders sub folders as ordered array
+ */
+function updateVisibleObjects(subFolders: string[]) {
+  objectState.visibleObjects = [];
+  let currentFolder = folderStructure.value;
+  for (const subFolder of subFolders) {
+    if (currentFolder.subFolders[subFolder] == null) {
+      return;
+    } else {
+      currentFolder = currentFolder.subFolders[subFolder];
+    }
+  }
+  const arr = [];
+  arr.push(...currentFolder.files);
+  arr.push(
+    ...Object.keys(currentFolder.subFolders).map((subFolderName) => {
+      const folderSize = calculateFolderSize(
+        currentFolder.subFolders[subFolderName]
+      );
+      const folderLastModified = dayjs(
+        calculateFolderLastModified(currentFolder.subFolders[subFolderName])
+      ).toISOString();
+      return {
+        name: subFolderName,
+        size: folderSize,
+        key: subFolderName,
+        parentFolder: subFolders,
+        last_modified: folderLastModified,
+      } as S3PseudoFolder;
+    })
+  );
+  objectState.visibleObjects = arr;
+}
+
+function isS3Object(
+  obj: S3PseudoFolder | S3ObjectWithFolder
+): obj is S3ObjectWithFolder {
+  return (obj as S3ObjectWithFolder).folder !== undefined;
+}
 </script>
 
 <template>
+  <!-- Navbar Breadcrumb -->
   <nav aria-label="breadcrumb" class="fs-2">
     <ol class="breadcrumb">
-      <li class="breadcrumb-item" :class="{ active: sub_folder_in_url }">
+      <li class="breadcrumb-item" :class="{ active: subFolderInUrl }">
         <router-link
-          v-if="sub_folder_in_url"
+          v-if="subFolderInUrl"
           :to="{
             name: 'bucket',
-            params: { bucket_name: route.params.bucket_name, sub_folders: [] },
+            params: { bucketName: route.params.bucketName, subFolders: [] },
           }"
-          >{{ route.params.bucket_name }}</router-link
-        >
-        <span v-else>{{ route.params.bucket_name }}</span>
+          >{{ route.params.bucketName }}
+        </router-link>
+        <span v-else>{{ route.params.bucketName }}</span>
       </li>
       <li
         class="breadcrumb-item"
-        v-for="(folder, index) in route.params.sub_folders"
+        v-for="(folder, index) in route.params.subFolders"
         :key="folder"
-        :class="{ active: index === route.params.sub_folders.length }"
+        :class="{ active: index === route.params.subFolders.length }"
       >
         <router-link
-          v-if="index !== route.params.sub_folders.length - 1"
+          v-if="index !== route.params.subFolders.length - 1"
           :to="{
             name: 'bucket',
             params: {
-              bucket_name: route.params.bucket_name,
-              sub_folders: route.params.sub_folders.slice(0, index + 1),
+              bucketName: route.params.bucketName,
+              subFolders: route.params.subFolders.slice(0, index + 1),
             },
           }"
-          >{{ folder }}</router-link
-        >
+          >{{ folder }}
+        </router-link>
         <span v-else>{{ folder }}</span>
       </li>
     </ol>
   </nav>
+  <!-- Inputs on top -->
+  <!-- Search bucket text input -->
   <div class="input-group mt-2">
     <span class="input-group-text" id="objects-search-wrapping"
       ><bootstrap-icon icon="search" :width="16" :height="16"
@@ -112,18 +311,20 @@ function update_objects(bucket_name: string) {
       disabled
     />
   </div>
+  <!-- Upload object button -->
   <button
     type="button"
     class="btn btn-secondary m-2"
-    :disabled="error_loading_objects"
+    :disabled="errorLoadingObjects"
   >
     <bootstrap-icon icon="upload" :width="16" :height="16" fill="white" />
     <span class="visually-hidden">Upload Object</span>
   </button>
+  <!-- Add bucket permission button -->
   <button
     type="button"
     class="btn btn-secondary m-2"
-    :disabled="error_loading_objects"
+    :disabled="errorLoadingObjects"
   >
     <bootstrap-icon
       icon="person-plus-fill"
@@ -133,30 +334,36 @@ function update_objects(bucket_name: string) {
     />
     <span class="visually-hidden">Add Bucket Permission</span>
   </button>
+  <!-- Add folder button -->
   <button
     type="button"
     class="btn btn-secondary m-2"
-    :disabled="error_loading_objects"
+    :disabled="errorLoadingObjects"
   >
     <bootstrap-icon icon="plus-lg" :width="16" :height="16" fill="white" />
     Folder
     <span class="visually-hidden">Add Folder</span>
   </button>
+  <!-- Body -->
   <div class="pt-3">
-    <div v-if="objectState.bucket_not_found_error">
+    <!-- If bucket not found -->
+    <div v-if="objectState.bucketNotFoundError">
       <p>Bucket not found</p>
     </div>
-    <div v-else-if="objectState.bucket_permission_error">
+    <!-- If no permission for bucket -->
+    <div v-else-if="objectState.bucketPermissionError">
       <p>No permission for this bucket</p>
     </div>
+    <!-- Show content of bucket -->
     <div v-else>
+      <!-- Table header -->
       <table
         class="table table-dark table-striped table-hover caption-top align-middle"
       >
         <caption>
           Displaying
           {{
-            objectState.loading ? 0 : objectState.objects.length
+            objectState.loading ? 0 : objectState.visibleObjects.length
           }}
           Objects
         </caption>
@@ -168,6 +375,7 @@ function update_objects(bucket_name: string) {
             <th scope="col"></th>
           </tr>
         </thead>
+        <!-- Table body when loading the objects -->
         <tbody v-if="objectState.loading">
           <tr v-for="n in 5" :key="n" class="placeholder-glow">
             <th scope="row">
@@ -178,22 +386,44 @@ function update_objects(bucket_name: string) {
             <td></td>
           </tr>
         </tbody>
-        <tbody v-else-if="objectState.objects.length === 0">
+        <!-- Table body when no objects are in the bcuket -->
+        <tbody v-else-if="objectState.visibleObjects.length === 0">
           <tr>
-            <td colspan="4" class="text-center text-secondary">
-              <i>No objects to display</i>
+            <td colspan="4" class="text-center fst-italic fw-light">
+              No objects to display
             </td>
           </tr>
         </tbody>
+        <!-- Table body when showing objects -->
         <tbody v-else>
-          <tr v-for="obj in objectState.objects" :key="obj.key">
-            <th scope="row" class="text-truncate">{{ obj.key }}</th>
+          <tr v-for="obj in objectState.visibleObjects" :key="obj.key">
+            <th scope="row" class="text-truncate">
+              <!-- Show file name if row is an object -->
+              <div v-if="isS3Object(obj)">{{ obj.pseudoFileName }}</div>
+              <!-- Show link to subfolder if row is a folder -->
+              <div v-else>
+                <router-link
+                  class="text-decoration-none"
+                  :to="{
+                    name: 'bucket',
+                    params: {
+                      bucketName: route.params.bucketName,
+                      subFolders: obj.parentFolder.concat(obj.name),
+                    },
+                  }"
+                  >{{ obj.name }}
+                </router-link>
+              </div>
+            </th>
             <td>{{ dayjs(obj.last_modified).fromNow() }}</td>
             <td>{{ fileSize(obj.size) }}</td>
+            <!-- Show buttons with dropdown menu if row is an object -->
             <td class="text-end">
               <div
+                v-if="isS3Object(obj)"
                 class="btn-group btn-group-sm dropdown-center dropdown-menu-start"
               >
+                <!-- Download Button -->
                 <button type="button" class="btn btn-secondary">
                   Download
                 </button>
@@ -205,7 +435,7 @@ function update_objects(bucket_name: string) {
                 >
                   <span class="visually-hidden">Toggle Dropdown</span>
                 </button>
-
+                <!-- Dropdown menu -->
                 <ul class="dropdown-menu dropdown-menu-dark">
                   <li>
                     <button class="dropdown-item" type="button">Details</button>
@@ -217,7 +447,10 @@ function update_objects(bucket_name: string) {
                     <button class="dropdown-item" type="button">Copy</button>
                   </li>
                   <li>
-                    <button class="dropdown-item text-danger" type="button">
+                    <button
+                      class="dropdown-item text-danger align-middle"
+                      type="button"
+                    >
                       <bootstrap-icon
                         icon="trash-fill"
                         class="text-danger"
@@ -230,6 +463,22 @@ function update_objects(bucket_name: string) {
                   </li>
                 </ul>
               </div>
+              <!-- Show delete button when row is a folder -->
+              <div v-else>
+                <button
+                  type="button"
+                  class="btn btn-danger btn-sm align-middle"
+                >
+                  <bootstrap-icon
+                    icon="trash-fill"
+                    class="text-danger me-2"
+                    :width="12"
+                    :height="12"
+                    fill="white"
+                  />
+                  <span>Delete</span>
+                </button>
+              </div>
             </td>
           </tr>
         </tbody>
diff --git a/src/components/CreateBucketComponent.vue b/src/components/CreateBucketComponent.vue
index c71438c9c08cb1065fe965dfe96e3b5a07f4221d..7980d6757d9cc68626b869215e3ab2b22781ad3c 100644
--- a/src/components/CreateBucketComponent.vue
+++ b/src/components/CreateBucketComponent.vue
@@ -3,11 +3,13 @@ import { BucketService } from "@/client";
 import type { BucketIn } from "@/client";
 import { reactive } from "vue";
 import BootstrapModal from "@/components/BootstrapModal.vue";
+import { useRouter } from "vue-router";
 /*
 import { onMounted } from "vue";
 import { Modal } from "bootstrap";
 */
 
+const router = useRouter();
 const emit = defineEmits(["bucketCreated"]);
 const bucket = reactive({ name: "", description: "" } as BucketIn);
 const formState = reactive({
@@ -51,6 +53,10 @@ function createBucket() {
         bucket.description = "";
         formState.bucketNameTaken = false;
         formState.validated = false;
+        router.push({
+          name: "bucket",
+          params: { bucketName: createdBucket.name, subFolders: [] },
+        });
       })
       .catch((error) => {
         if (
@@ -149,7 +155,12 @@ function modalClosed() {
         :disabled="formState.loading"
         @click.prevent="createBucket"
       >
-        <span v-if="formState.loading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
+        <span
+          v-if="formState.loading"
+          class="spinner-border spinner-border-sm"
+          role="status"
+          aria-hidden="true"
+        ></span>
         Save
       </button>
     </template>
diff --git a/src/router/index.ts b/src/router/index.ts
index 094becc767a5ca50676e6286e8b3451e286598c0..1c4428c933dc41bb3a1a604ed6396e3c1949b02c 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -16,7 +16,7 @@ const router = createRouter({
           component: () => import("../views/object-storage/BucketsView.vue"),
           children: [
             {
-              path: ":bucket_name/:sub_folders*",
+              path: ":bucketName/:subFolders*",
               name: "bucket",
               component: () => import("../components/BucketView.vue"),
             },
diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue
index ad8942406104e436552ab3e2f07f5c0173bee8f2..a35fab70862ae2bf0008765e585947c598785469 100644
--- a/src/views/object-storage/BucketsView.vue
+++ b/src/views/object-storage/BucketsView.vue
@@ -32,11 +32,11 @@ function addBucket(bucket: BucketOut) {
   bucketsState.buckets.push(bucket);
 }
 
-function deleteBucket(bucket_name: string) {
-  BucketService.bucketDeleteBucket(bucket_name).then(() => {
+function deleteBucket(bucketName: string) {
+  BucketService.bucketDeleteBucket(bucketName).then(() => {
     router.push({ name: "buckets" });
     bucketsState.buckets = bucketsState.buckets.filter(
-      (bucket) => bucket.name !== bucket_name
+      (bucket) => bucket.name !== bucketName
     );
   });
 }
@@ -97,8 +97,8 @@ onMounted(() => {
             v-for="bucket in bucketsState.buckets"
             :key="bucket.name"
             :active="
-              route.params.bucket_name != null &&
-              route.params.bucket_name === bucket.name
+              route.params.bucketName != null &&
+              route.params.bucketName === bucket.name
             "
             :bucket="bucket"
             :loading="false"