diff --git a/client/angular.json b/client/angular.json
index 3c6e06942e831b94ea0acd7276d679ce69329a44..1df04ec6cef2a25f24731f07fae0448bf96b9fd1 100644
--- a/client/angular.json
+++ b/client/angular.json
@@ -28,7 +28,8 @@
             "inlineStyleLanguage": "scss",
             "assets": [
               "src/favicon.ico",
-              "src/assets"
+              "src/assets",
+              { "glob": "**/*", "input": "node_modules/tinymce", "output": "/tinymce/" }
             ],
             "styles": [
               "node_modules/@fortawesome/fontawesome-free/css/all.css",
diff --git a/client/package.json b/client/package.json
index c085d335281027eb0572f85cac2166d3d66ae3e1..7cb731f859fd11ffa5b83216bc87523a75d9b7ea 100644
--- a/client/package.json
+++ b/client/package.json
@@ -24,15 +24,18 @@
     "@ngrx/router-store": "13.0.2",
     "@ngrx/store": "13.0.2",
     "@ngrx/store-devtools": "13.0.2",
+    "@tinymce/tinymce-angular": "^6.0.1",
     "bootstrap": "4.6.1",
     "d3": "^5.15.1",
     "file-saver": "^2.0.5",
     "keycloak-angular": "^9.1.0",
     "keycloak-js": "^16.1.1",
     "ngx-bootstrap": "^8.0.0",
+    "ngx-dynamic-hooks": "^2.0.3",
     "ngx-json-viewer": "^3.0.2",
     "ngx-toastr": "^14.2.1",
     "rxjs": "~7.5.0",
+    "tinymce": "^6.0.3",
     "tslib": "^2.3.0",
     "zone.js": "~0.11.4"
   },
diff --git a/client/src/app/admin/admin.component.html b/client/src/app/admin/admin.component.html
index bd1e260acf97f000884a585cceef5991791c07e7..5c884c3b52f4a5225b2b6596965fb0ac8845b72c 100644
--- a/client/src/app/admin/admin.component.html
+++ b/client/src/app/admin/admin.component.html
@@ -1,17 +1,12 @@
 <header>
-    <app-navbar
-        [links]="links"
+    <app-admin-navbar
         [isAuthenticated]="isAuthenticated | async"
         [userProfile]="userProfile | async"
-        [userRoles]="userRoles | async"
-        [baseHref]="getBaseHref()"
         [authenticationEnabled]="getAuthenticationEnabled()"
-        [adminRoles]="getAdminRoles()"
-        [url]="url | async"
         (login)="login()"
         (logout)="logout()"
         (openEditProfile)="openEditProfile()">
-    </app-navbar>
+    </app-admin-navbar>
 </header>
 <main role="main" class="container-fluid pb-4">
     <router-outlet></router-outlet>
diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts
index d8f886749a1519025e28d8556ec5207b4e81095c..3c55077ed60f028e2af755b90491a0becd7770c4 100644
--- a/client/src/app/admin/admin.component.ts
+++ b/client/src/app/admin/admin.component.ts
@@ -11,7 +11,6 @@ import { Component, OnInit } from '@angular/core';
 
 import { Observable } from 'rxjs';
 import { Store } from '@ngrx/store';
-import * as fromRouter from '@ngrx/router-store';
 
 import { UserProfile } from 'src/app/auth/user-profile.model';
 import * as authActions from 'src/app/auth/auth.actions';
@@ -25,7 +24,7 @@ import { AppConfigService } from 'src/app/app-config.service';
 })
 /**
  * @class
- * @classdesc Portal home container.
+ * @classdesc Admin container.
  *
  * @implements OnInit
  */
@@ -33,20 +32,12 @@ export class AdminComponent implements OnInit {
     public favIcon: HTMLLinkElement = document.querySelector('#favicon');
     public title: HTMLLinkElement = document.querySelector('#title');
     public body: HTMLBodyElement = document.querySelector('body');
-    public links = [
-        { label: 'Instances', icon: 'fas fa-object-group', routerLink: 'instance/instance-list' },
-        { label: 'Databases', icon: 'fas fa-database', routerLink: 'database/database-list'}
-    ];
     public isAuthenticated: Observable<boolean>;
     public userProfile: Observable<UserProfile>;
-    public userRoles: Observable<string[]>;
-    public url: Observable<string>;
 
     constructor(private store: Store<{ }>, private config: AppConfigService) {
         this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
         this.userProfile = store.select(authSelector.selectUserProfile);
-        this.userRoles = store.select(authSelector.selectUserRoles);
-        this.url = store.select(fromRouter.getSelectors().selectUrl);
     }
 
     ngOnInit() {
@@ -56,18 +47,10 @@ export class AdminComponent implements OnInit {
         Promise.resolve(null).then(() => this.store.dispatch(databaseActions.loadDatabaseList()));
     }
 
-    getBaseHref() {
-        return this.config.baseHref;
-    }
-
     getAuthenticationEnabled() {
         return this.config.authenticationEnabled;
     }
 
-    getAdminRoles(): string[] {
-        return this.config.adminRoles;
-    }
-
     login(): void {
         this.store.dispatch(authActions.login({ redirectUri: window.location.toString() }));
     }
diff --git a/client/src/app/admin/admin.module.ts b/client/src/app/admin/admin.module.ts
index fc26cfe972c1cf7866d9180daa6b057dc56ac238..432b436da0520a466142cc0557cbda93913ef495 100644
--- a/client/src/app/admin/admin.module.ts
+++ b/client/src/app/admin/admin.module.ts
@@ -15,6 +15,7 @@ import { EffectsModule } from '@ngrx/effects';
 import { SharedModule } from 'src/app/shared/shared.module';
 import { AdminSharedModule } from './admin-shared/admin-shared.module';
 import { AdminRoutingModule, routedComponents } from './admin-routing.module';
+import { dummiesComponents } from './components';
 import { adminReducer } from './admin.reducer';
 import { adminEffects } from './store/effects';
 import { adminServices } from './store/services';
@@ -28,7 +29,8 @@ import { adminServices } from './store/services';
         EffectsModule.forFeature(adminEffects)
     ],
     declarations: [
-        routedComponents
+        routedComponents,
+        dummiesComponents
     ],
     providers: [
         adminServices
diff --git a/client/src/app/shared/components/navbar.component.html b/client/src/app/admin/components/admin-navbar.component.html
similarity index 63%
rename from client/src/app/shared/components/navbar.component.html
rename to client/src/app/admin/components/admin-navbar.component.html
index 347d4b7b7f1480d137d18442569f7f47c6f58426..155e4ffc30fb23b2a7bd94425beda48fddc04b8e 100644
--- a/client/src/app/shared/components/navbar.component.html
+++ b/client/src/app/admin/components/admin-navbar.component.html
@@ -1,35 +1,31 @@
 <nav class="navbar navbar-light bg-light navbar-expand-md fixed-top border-bottom">
     <!-- Logo -->
-    <a *ngIf="!instance" href="{{ baseHref }}" class="navbar-brand">
+    <a routerLink="/admin" class="navbar-brand">
         <img src="assets/cesam_anis40.png" alt="ANIS logo" />
     </a>
 
-    <a *ngIf="instance" routerLink="/instance/{{ instance.name }}" class="navbar-brand">
-        <img *ngIf="instance.public" [src]="getLogoHref()" alt="Instance logo" />
-        <img *ngIf="!instance.public" [src]="getLogoHref() | authImage | async" alt="Instance logo" />
-    </a>
-
-    <!-- Right Navigation -->
+    <!-- Navigation -->
     <div class="collapse navbar-collapse" id="navbarCollapse">
         <ul class="navbar-nav mr-auto">
-            <li *ngFor="let link of links" class="nav-item pr-3">
-                <a class="nav-link" [routerLink]="link.routerLink" routerLinkActive="active">
-                    <span [ngClass]="link.icon"></span> {{ link.label }}
+            <li class="nav-item pr-3">
+                <a class="nav-link" routerLink="instance/instance-list" routerLinkActive="active">
+                    <span class="fas fa-object-group"></span> Instances
+                </a>
+            </li>
+            <li class="nav-item pr-3">
+                <a class="nav-link" routerLink="database/database-list" routerLinkActive="active">
+                    <span class="fas fa-database"></span> Databases
                 </a>
             </li>
         </ul>
         <ul class="navbar-nav justify-content-end">
             <li class="nav-item pr-3">
-                <a *ngIf="isAdminRoute() || (instance && instance.back_to_portal)" class="nav-link" routerLink="/portal" routerLinkActive="active">
+                <a class="nav-link" routerLink="/portal" routerLinkActive="active">
                     <span class="fa-solid fa-right-to-bracket"></span> Back to portal
                 </a>
             </li>
-            <li *ngIf="!authenticationEnabled && !isAdminRoute()" class="nav-item pr-3">
-                <a class="nav-link" routerLink="/admin" routerLinkActive="active">
-                    <span class="fas fa-tools"></span> Admin
-                </a>
-            </li>
         </ul>
+        <!-- sign in / sign out -->
         <button *ngIf="authenticationEnabled && !isAuthenticated"
             class="btn btn-outline-success my-2 my-sm-0"
             id="button-sign-in"
@@ -38,7 +34,7 @@
         </button>
         <span *ngIf="isAuthenticated" id="dropdown-menu" dropdown>
             <button id="button-basic" dropdownToggle type="button" class="btn btn-light" aria-controls="dropdown-basic">
-                <span class="fa-stack" [ngStyle]="{ color: instance ? instance.design_color : '#7AC29A' }">
+                <span class="fa-stack" [ngStyle]="{ color: '#7AC29A' }">
                     <span class="fas fa-circle fa-2x"></span>
                     <span class="fas fa-user fa-stack-1x fa-inverse"></span>
                 </span>
@@ -51,9 +47,6 @@
                 </li>
                 <li class="divider dropdown-divider"></li>
                 <li role="menuitem">
-                    <a *ngIf="isAdmin() && !isAdminRoute()" class="dropdown-item pointer" routerLink="/admin">
-                        <span class="fas fa-tools"></span> Admin
-                    </a>
                     <a class="dropdown-item pointer" (click)="openEditProfile.emit()">
                         <span class="fas fa-id-card"></span> Edit profile
                     </a>
@@ -68,7 +61,7 @@
         </span>
     </div>
 
-    <!-- Dropdown appearing on mobile only -->
+    <!-- Navigation Mobile -->
     <span dropdown>
         <button id="button-basic" dropdownToggle type="button" class="navbar-toggler" aria-controls="dropdown-basic">
             <span class="fas fa-bars"></span>
@@ -78,27 +71,24 @@
             <li *ngIf="isAuthenticated" role="menuitem">
                 <span class="dropdown-item font-italic">{{ userProfile.email }}</span>
             </li>
-            <li *ngIf="isAuthenticated && links.length > 0" class="divider dropdown-divider"></li>
-            <li *ngFor="let link of links" role="menuitem">
-                <a class="dropdown-item" [routerLink]="link.routerLink">
-                    <span [ngClass]="link.icon" class="fa-fw"></span> {{ link.label }}
+            <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li>
+            <li role="menuitem">
+                <a class="dropdown-item" routerLink="instance/instance-list">
+                    <span class="fas fa-object-group fa-fw"></span> Instances
                 </a>
             </li>
             <li role="menuitem">
-                <a *ngIf="!isPortalRoute()" class="dropdown-item" routerLink="/portal">
-                    <span class="fa-solid fa-right-to-bracket fa-fw"></span> Back to portal
+                <a class="dropdown-item" routerLink="database/database-list">
+                    <span class="fas fa-database fa-fw"></span> Databases
                 </a>
             </li>
-            <li *ngIf="isAuthenticated || (!authenticationEnabled && !isAdminRoute())" class="divider dropdown-divider"></li>
-            <li *ngIf="!authenticationEnabled && !isAdminRoute()" role="menuitem">
-                <a class="dropdown-item pointer" routerLink="/admin">
-                    <span class="fas fa-tools"></span> Admin
+            <li role="menuitem">
+                <a class="dropdown-item" routerLink="/portal">
+                    <span class="fa-solid fa-right-to-bracket fa-fw"></span> Back to portal
                 </a>
             </li>
+            <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li>
             <li *ngIf="isAuthenticated" role="menuitem">
-                <a *ngIf="isAdmin() && !isAdminRoute()" class="dropdown-item pointer" routerLink="/admin">
-                    <span class="fas fa-tools"></span> Admin
-                </a>
                 <a class="dropdown-item pointer" (click)="openEditProfile.emit()">
                     <span class="fas fa-id-card"></span> Edit profile
                 </a>
diff --git a/client/src/app/shared/components/navbar.component.scss b/client/src/app/admin/components/admin-navbar.component.scss
similarity index 100%
rename from client/src/app/shared/components/navbar.component.scss
rename to client/src/app/admin/components/admin-navbar.component.scss
diff --git a/client/src/app/admin/components/admin-navbar.component.ts b/client/src/app/admin/components/admin-navbar.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e61c3efdf1d468c8bdfbd0aeecbb63457af637b
--- /dev/null
+++ b/client/src/app/admin/components/admin-navbar.component.ts
@@ -0,0 +1,18 @@
+import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core';
+
+import { UserProfile } from 'src/app/auth/user-profile.model';
+
+@Component({
+    selector: 'app-admin-navbar',
+    templateUrl: 'admin-navbar.component.html',
+    styleUrls: [ 'admin-navbar.component.scss' ],
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class AdminNavbarComponent {
+    @Input() isAuthenticated: boolean;
+    @Input() userProfile: UserProfile = null;
+    @Input() authenticationEnabled: boolean;
+    @Output() login: EventEmitter<any> = new EventEmitter();
+    @Output() logout: EventEmitter<any> = new EventEmitter();
+    @Output() openEditProfile: EventEmitter<any> = new EventEmitter();
+}
diff --git a/client/src/app/admin/components/index.ts b/client/src/app/admin/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f66c3652c6a8d53e08910ceeddbbdfbb1e11b2c7
--- /dev/null
+++ b/client/src/app/admin/components/index.ts
@@ -0,0 +1,14 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { AdminNavbarComponent } from './admin-navbar.component';
+
+export const dummiesComponents = [
+    AdminNavbarComponent
+];
diff --git a/client/src/app/admin/instance/components/instance-form.component.html b/client/src/app/admin/instance/components/instance-form.component.html
index 531367d983834932b80a5f0aba596ae582197c62..0dc13e93526e8680cb447761feace9c965ad3ce9 100644
--- a/client/src/app/admin/instance/components/instance-form.component.html
+++ b/client/src/app/admin/instance/components/instance-form.component.html
@@ -114,31 +114,6 @@
                 (loadDirectory)="onChangeFileSelect($event)">
             </app-path-select-form-control>
         </accordion-group>
-        <accordion-group heading="Home page" [isOpen]="true">
-            <div class="form-group">
-                <label for="home_component">Component</label>
-                <select class="form-control" name="home_component" formControlName="home_component">
-                    <option value="WelcomeComponent">WelcomeComponent</option>
-                </select>
-            </div>
-            <div formGroupName="home_component_config">
-                <div class="form-group">
-                    <label for="home_component_text">Text</label>
-                    <textarea class="form-control" id="home_component_text" formControlName="home_component_text" rows="3"></textarea>
-                </div>
-                <app-path-select-form-control
-                    [form]="getHomeConfigFormGroup()"
-                    [disabled]="isFilesPathEmpty()"
-                    [controlName]="'home_component_logo'"
-                    [controlLabel]="'Logo'"
-                    [files]="files"
-                    [filesIsLoading]="filesIsLoading"
-                    [filesIsLoaded]="filesIsLoaded"
-                    [selectType]="'file'"
-                    (loadDirectory)="onChangeFileSelect($event)">
-                </app-path-select-form-control>
-            </div>
-        </accordion-group>
         <accordion-group heading="Functionalities" [isOpen]="true">
             <div class="custom-control custom-switch mb-2">
                 <input class="custom-control-input" type="checkbox" id="samp_enabled" name="samp_enabled" formControlName="samp_enabled">
diff --git a/client/src/app/admin/instance/components/instance-form.component.ts b/client/src/app/admin/instance/components/instance-form.component.ts
index 22f1c6d21f082ebaba3c77343d9d1b79463b75fe..00cb22c3a827821bf1144b1624fce6c5b3b011db 100644
--- a/client/src/app/admin/instance/components/instance-form.component.ts
+++ b/client/src/app/admin/instance/components/instance-form.component.ts
@@ -41,13 +41,6 @@ export class InstanceFormComponent implements OnInit {
         design_background_color: new FormControl('#FFFFFF', [Validators.required]),
         design_logo: new FormControl(''),
         design_favicon: new FormControl(''),
-        home_component: new FormControl('WelcomeComponent', [Validators.required]),
-        home_component_config: new FormGroup({
-            home_component_text: new FormControl(`AstroNomical Information System is a generic web tool aimed 
-at facilitating and homogenizing the implementation of astronomical data. It allows 
-the fast implementation of a project data exchange platform in a dedicated information system.`, [Validators.required]),
-            home_component_logo: new FormControl('home_component_logo.png', [Validators.required])
-        }),
         samp_enabled: new FormControl(true),
         back_to_portal: new FormControl(true),
         search_by_criteria_allowed: new FormControl(true),
diff --git a/client/src/app/admin/instance/containers/configure-instance.component.ts b/client/src/app/admin/instance/containers/configure-instance.component.ts
index ace89da19d750cce42ac7d6458b2dd269ce02335..1facdceaa375aac63137b514d1b9edc9de8e9e98 100644
--- a/client/src/app/admin/instance/containers/configure-instance.component.ts
+++ b/client/src/app/admin/instance/containers/configure-instance.component.ts
@@ -13,6 +13,8 @@ import { Store } from '@ngrx/store';
 import * as datasetFamilyActions from 'src/app/metamodel/actions/dataset-family.actions';
 import * as datasetActions from 'src/app/metamodel/actions/dataset.actions';
 import * as datasetGroupActions from 'src/app/metamodel/actions/dataset-group.actions';
+import * as webpageFamilyActions from 'src/app/metamodel/actions/webpage-family.actions';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
 
 @Component({
     selector: 'app-configure-instance',
@@ -25,5 +27,7 @@ export class ConfigureInstanceComponent implements OnInit {
         Promise.resolve(null).then(() => this.store.dispatch(datasetFamilyActions.loadDatasetFamilyList()));
         Promise.resolve(null).then(() => this.store.dispatch(datasetActions.loadDatasetList()));
         Promise.resolve(null).then(() => this.store.dispatch(datasetGroupActions.loadDatasetGroupList()));
+        Promise.resolve(null).then(() => this.store.dispatch(webpageFamilyActions.loadWebpageFamilyList()));
+        Promise.resolve(null).then(() => this.store.dispatch(webpageActions.loadWebpageList()));
     }
 }
diff --git a/client/src/app/admin/instance/dataset/components/instance-buttons.component.html b/client/src/app/admin/instance/dataset/components/instance-buttons.component.html
index 89218e04727878415c9c8a3856bf101097b9e6f0..cfa2e25ec324e12edae22d41f56cdcbd0b998ed7 100644
--- a/client/src/app/admin/instance/dataset/components/instance-buttons.component.html
+++ b/client/src/app/admin/instance/dataset/components/instance-buttons.component.html
@@ -1,10 +1,15 @@
 <div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups">
-    <div class="btn-group mr-2" role="group" aria-label="First group">
+    <div class="btn-group mr-2" role="group" aria-label="New Dataset family">
         <button (click)="openModal(template); $event.stopPropagation()" title="Add new dataset family" class="btn btn-outline-success">
             <span class="fas fa-plus"></span> New dataset family
         </button>
     </div>
-    <div class="btn-group mr-2" role="group" aria-label="Second group">
+    <div class="btn-group mr-2" role="group" aria-label="Edit webpages">
+        <button routerLink="/admin/instance/configure-instance/{{ instance.name }}/webpage" title="Edit instances webpages" class="btn btn-outline-primary">
+            <span class="fas fa-file-lines"></span> Edit instance webpages
+        </button>
+    </div>
+    <div class="btn-group mr-2" role="group" aria-label="Handle dataset groups">
         <button routerLink="/admin/instance/configure-instance/{{ instance.name }}/dataset-group" title="Handle groups" class="btn btn-outline-primary">
             <span class="fas fa-users"></span> Handle dataset groups
         </button>
diff --git a/client/src/app/admin/instance/instance-routing.module.ts b/client/src/app/admin/instance/instance-routing.module.ts
index d7f78cc8e9869a9fe8744a108db608bfdb3fca2f..1c39ca08d56b458fb9c5b30a3d68447d038efeba 100644
--- a/client/src/app/admin/instance/instance-routing.module.ts
+++ b/client/src/app/admin/instance/instance-routing.module.ts
@@ -29,7 +29,8 @@ const routes: Routes = [
         [
             { path: '', redirectTo: 'dataset/dataset-list', pathMatch: 'full' },
             { path: 'dataset', loadChildren: () => import('./dataset/dataset.module').then(m => m.DatasetModule) },
-            { path: 'dataset-group', loadChildren: () => import('./dataset-group/dataset-group.module').then(m => m.DatasetGroupModule) }
+            { path: 'dataset-group', loadChildren: () => import('./dataset-group/dataset-group.module').then(m => m.DatasetGroupModule) },
+            { path: 'webpage', loadChildren: () => import('./webpage/webpage.module').then(m => m.WebpageModule) },
         ]
     },
 ];
diff --git a/client/src/app/admin/instance/webpage/components/edit-webpage-family.component.html b/client/src/app/admin/instance/webpage/components/edit-webpage-family.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..b465fe5e7d0343a7d3ca979ea6d11a03689de402
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/edit-webpage-family.component.html
@@ -0,0 +1,18 @@
+<button title="Edit this webpage family" (click)="openModal(template)" class="btn btn-outline-primary">
+    <span class="fas fa-edit"></span>
+</button>
+
+<ng-template #template>
+    <div class="modal-header">
+        <h4 class="modal-title pull-left"><strong>Edit webpage family</strong></h4>
+    </div>
+    <div class="modal-body">
+        <app-webpage-family-form [webpageFamily]="webpageFamily" (onSubmit)="onSubmit.emit($event)" #formEditWebpageFamily>
+            <button [disabled]="!formEditWebpageFamily.form.valid || formEditWebpageFamily.form.pristine" (click)="modalRef.hide()" type="submit" class="btn btn-primary">
+                <span class="fa fa-database"></span> Edit webpage family
+            </button>
+            &nbsp;
+            <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button>
+        </app-webpage-family-form>
+    </div>
+</ng-template>
diff --git a/client/src/app/admin/instance/webpage/components/edit-webpage-family.component.ts b/client/src/app/admin/instance/webpage/components/edit-webpage-family.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3333361a9b4e9b6f749221453edfe83cfd369e56
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/edit-webpage-family.component.ts
@@ -0,0 +1,33 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input, ChangeDetectionStrategy, TemplateRef, Output, EventEmitter } from '@angular/core';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+
+import { WebpageFamily } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-edit-webpage-family',
+    templateUrl: 'edit-webpage-family.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class EditWebpageFamilyComponent {
+    @Input() webpageFamily: WebpageFamily;
+    @Output() onSubmit: EventEmitter<WebpageFamily> = new EventEmitter();
+
+    modalRef: BsModalRef;
+
+    constructor(private modalService: BsModalService) { }
+
+    openModal(template: TemplateRef<any>) {
+        this.modalRef = this.modalService.show(template);
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/components/index.ts b/client/src/app/admin/instance/webpage/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..401f94b17e72569f4c4615a909480360b70ab631
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/index.ts
@@ -0,0 +1,24 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { WebpageButtonsComponent } from './webpage-buttons.component';
+import { WebpageFamilyCardComponent } from './webpage-family-card.component';
+import { EditWebpageFamilyComponent } from './edit-webpage-family.component';
+import { WebpageFamilyFormComponent } from './webpage-family-form.component';
+import { WebpageCardComponent } from './webpage-card.component';
+import { WebpageFormComponent } from './webpage-form.component';
+
+export const dummiesComponents = [
+    WebpageButtonsComponent,
+    WebpageFamilyCardComponent,
+    EditWebpageFamilyComponent,
+    WebpageFamilyFormComponent,
+    WebpageCardComponent,
+    WebpageFormComponent
+];
diff --git a/client/src/app/admin/instance/webpage/components/webpage-buttons.component.html b/client/src/app/admin/instance/webpage/components/webpage-buttons.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..b755d6b2da4daf7dbca7904df3879506ee09d378
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-buttons.component.html
@@ -0,0 +1,22 @@
+<div class="btn-toolbar mb-3" role="toolbar">
+    <div class="btn-group mr-2" role="group">
+        <button (click)="openModal(template); $event.stopPropagation()" title="Add new webpage family" class="btn btn-outline-success">
+            <span class="fas fa-plus"></span> New webpage family
+        </button>
+    </div>
+</div>
+
+<ng-template #template>
+    <div class="modal-header">
+        <h4 class="modal-title pull-left"><strong>Add a new webpage family</strong></h4>
+    </div>
+    <div class="modal-body">
+        <app-webpage-family-form (onSubmit)="addWebpageFamily.emit($event)" #formAddWebpageFamily>
+            <button [disabled]="!formAddWebpageFamily.form.valid || formAddWebpageFamily.form.pristine" (click)="modalRef.hide()" type="submit" class="btn btn-primary">
+                <span class="fa fa-database"></span> Add new webpage family
+            </button>
+            &nbsp;
+            <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button>
+        </app-webpage-family-form>
+    </div>
+</ng-template>
diff --git a/client/src/app/admin/instance/webpage/components/webpage-buttons.component.ts b/client/src/app/admin/instance/webpage/components/webpage-buttons.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c5ff64716d0c3af97c78b5388d56381cc13336a
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-buttons.component.ts
@@ -0,0 +1,32 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef, Input } from '@angular/core';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+
+import { WebpageFamily } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-buttons',
+    templateUrl: 'webpage-buttons.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class WebpageButtonsComponent {
+    @Output() addWebpageFamily: EventEmitter<WebpageFamily> = new EventEmitter();
+
+    modalRef: BsModalRef;
+
+    constructor(private modalService: BsModalService) { }
+
+    openModal(template: TemplateRef<any>) {
+        this.modalRef = this.modalService.show(template);
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/components/webpage-card.component.html b/client/src/app/admin/instance/webpage/components/webpage-card.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..96ec727676b0163e16c738392a1ecf073141df2f
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-card.component.html
@@ -0,0 +1,21 @@
+<div class="card">
+    <div class="card-body">
+        <h5 class="card-title font-weight-bold">{{ webpage.label }}</h5>
+        <ul class="card-text list-unstyled pl-4">
+            <li>Icon: <span [ngClass]="webpage.icon"></span></li>
+            <li>Title: <span class="badge badge-secondary">{{ webpage.title }}</span></li>
+            <li>Display: <span class="badge badge-secondary">{{ webpage.display }}</span></li>
+        </ul>
+    </div>
+    <div class="card-footer bg-transparent text-right">
+        <a title="Edit this webpage" routerLink="edit-webpage/{{webpage.id}}" class="btn btn-outline-primary">
+            <span class="fas fa-edit"></span>
+        </a>
+        &nbsp;
+        <app-delete-btn
+            [type]="'webpage'"
+            [label]="webpage.label"
+            (confirm)="deleteWebpage.emit(webpage)">
+        </app-delete-btn>
+    </div>
+</div>
diff --git a/client/src/app/admin/instance/webpage/components/webpage-card.component.scss b/client/src/app/admin/instance/webpage/components/webpage-card.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2325ac153e67f992d8334bb20def08bf574a347b
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-card.component.scss
@@ -0,0 +1,12 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+.card {
+    height: 200px;
+}
\ No newline at end of file
diff --git a/client/src/app/admin/instance/webpage/components/webpage-card.component.ts b/client/src/app/admin/instance/webpage/components/webpage-card.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ef52c705f90cba6a19d1b9229872a02852ebcae1
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-card.component.ts
@@ -0,0 +1,13 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+
+import { Webpage } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-card',
+    templateUrl: 'webpage-card.component.html',
+    styleUrls: [ 'webpage-card.component.scss' ]
+})
+export class WebpageCardComponent {
+    @Input() webpage: Webpage;
+    @Output() deleteWebpage: EventEmitter<Webpage> = new EventEmitter();
+}
diff --git a/client/src/app/admin/instance/webpage/components/webpage-family-card.component.html b/client/src/app/admin/instance/webpage/components/webpage-family-card.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e84682a97849f925e8d080ae925f83d5d5310c17
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-family-card.component.html
@@ -0,0 +1,40 @@
+<div class="card mb-3">
+    <div class="card-header">
+        <div class="row align-items-center">
+            <div class="col-md-10">
+                <span class="card-title font-weight-bold">{{ webpageFamily.label }}</span>
+                (Display : {{ webpageFamily.display }}, icon : <span [ngClass]="webpageFamily.icon"></span> )
+            </div>
+            <div class="col-md-2 text-right">
+                <app-edit-webpage-family
+                    [webpageFamily]="webpageFamily"
+                    (onSubmit)="editWebpageFamily.emit($event)">
+                </app-edit-webpage-family>
+                &nbsp;
+                <app-delete-btn
+                    [disabled]="nbWebpagesByWebpageFamily() > 0"
+                    [type]="'webpage-family'"
+                    [label]="webpageFamily.label"
+                    (confirm)="deleteWebpageFamily.emit(webpageFamily)">
+                </app-delete-btn>
+            </div>
+        </div>
+    </div>
+    <div class="card-body">
+        <div class="row row-cols-1 row-cols-sm-2 row-cols-md-3">
+            <div *ngFor="let webpage of (webpageList | webpageListByFamily:webpageFamily.id)" class="col mb-3">
+                <app-webpage-card 
+                    [webpage]="webpage"
+                    (deleteWebpage)="deleteWebpage.emit($event)">
+                </app-webpage-card>
+            </div>
+            <div class="col h-100 d-table">
+                <div routerLink="new-webpage" [queryParams]="{id_webpage_family: webpageFamily.id}" class="card card-add d-table-cell align-middle pointer" title="Add new webpage">
+                    <div class="card-body text-center">
+                        <span class="fas fa-plus fa-4x text-light"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/client/src/app/admin/instance/webpage/components/webpage-family-card.component.scss b/client/src/app/admin/instance/webpage/components/webpage-family-card.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..0ac62a8554a7e9c2218b358116ba288d7ec9e8d4
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-family-card.component.scss
@@ -0,0 +1,19 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+.card-add {
+    height: 200px;
+    background-color: #A8C96E;
+    transition: font-size 0.3s;
+}
+
+.card-add:hover {
+    background-color: #9dc25b;
+    font-size: 20px;
+}
diff --git a/client/src/app/admin/instance/webpage/components/webpage-family-card.component.ts b/client/src/app/admin/instance/webpage/components/webpage-family-card.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3a48c07775c727bad9b547e9f87873668655cc5
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-family-card.component.ts
@@ -0,0 +1,30 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter } from '@angular/core';
+
+import { WebpageFamily, Webpage } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-family-card',
+    templateUrl: 'webpage-family-card.component.html',
+    styleUrls: [ 'webpage-family-card.component.scss' ],
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class WebpageFamilyCardComponent {
+    @Input() webpageFamily: WebpageFamily;
+    @Input() webpageList: Webpage[];
+    @Output() editWebpageFamily: EventEmitter<WebpageFamily> = new EventEmitter();
+    @Output() deleteWebpageFamily: EventEmitter<WebpageFamily> = new EventEmitter();
+    @Output() deleteWebpage: EventEmitter<Webpage> = new EventEmitter();
+
+    nbWebpagesByWebpageFamily(): number {
+        return this.webpageList.filter(webpage => webpage.id_webpage_family === this.webpageFamily.id).length;
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/components/webpage-family-form.component.html b/client/src/app/admin/instance/webpage/components/webpage-family-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..12d5f5b845964d6b9b5bcd2c781b5a356e36cc0d
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-family-form.component.html
@@ -0,0 +1,17 @@
+<form [formGroup]="form" (ngSubmit)="submit()" novalidate>
+    <div class="form-group">
+        <label for="label">Label</label>
+        <input type="text" class="form-control" id="label" name="label" formControlName="label">
+    </div>
+    <div class="form-group">
+        <label for="icon">Icon</label>
+        <input type="text" class="form-control" id="icon" name="icon" formControlName="icon">
+    </div>
+    <div class="form-group">
+        <label for="display">Display</label>
+        <input type="number" class="form-control" id="display" name="display" formControlName="display">
+    </div>
+    <div class="form-group">
+        <ng-content></ng-content>
+    </div>
+</form>
diff --git a/client/src/app/admin/instance/webpage/components/webpage-family-form.component.ts b/client/src/app/admin/instance/webpage/components/webpage-family-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1449e3818a8511ddec635f3238e2f564be8333a8
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-family-form.component.ts
@@ -0,0 +1,45 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
+
+import { WebpageFamily } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-family-form',
+    templateUrl: 'webpage-family-form.component.html'
+})
+export class WebpageFamilyFormComponent implements OnInit {
+    @Input() webpageFamily: WebpageFamily;
+    @Output() onSubmit: EventEmitter<WebpageFamily> = new EventEmitter();
+
+    public form = new FormGroup({
+        label: new FormControl('', [Validators.required]),
+        icon: new FormControl(null),
+        display: new FormControl('', [Validators.required])
+    });
+
+    ngOnInit() {
+        if (this.webpageFamily) {
+            this.form.patchValue(this.webpageFamily);
+        }
+    }
+
+    submit() {
+        if (this.webpageFamily) {
+            this.onSubmit.emit({
+                ...this.webpageFamily,
+                ...this.form.value
+            });
+        } else {
+            this.onSubmit.emit(this.form.value);
+        }
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/components/webpage-form.component.html b/client/src/app/admin/instance/webpage/components/webpage-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..dd32c63dc6bf4c234bba7a80b738379bba5c285d
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-form.component.html
@@ -0,0 +1,33 @@
+<form [formGroup]="form" (ngSubmit)="submit()" novalidate>
+    <div class="form-group">
+        <label for="label">Label</label>
+        <input type="text" class="form-control" id="label" name="label" formControlName="label">
+    </div>
+    <div class="form-group">
+        <label for="icon">Icon</label>
+        <input type="text" class="form-control" id="icon" name="icon" formControlName="icon">
+    </div>
+    <div class="form-group">
+        <label for="display">Display</label>
+        <input type="number" class="form-control" id="display" name="display" formControlName="display">
+    </div>
+    <div class="form-group">
+        <label for="title">Title</label>
+        <input type="text" class="form-control" id="title" name="title" formControlName="title">
+    </div>
+    <div class="form-group">
+        <label for="id_webpage_family">Family</label>
+        <select class="form-control" id="id_webpage_family" name="id_webpage_family" formControlName="id_webpage_family">
+            <option></option>
+            <option *ngFor="let family of webpageFamilyList" [ngValue]="family.id">{{ family.label }}</option>
+        </select>
+    </div>
+    <div class="form-group">
+        <label for="content">Content</label>
+        <editor [init]="getEditorConfig()" formControlName="content">
+        </editor>
+    </div>
+    <div class="form-group pt-4">
+        <ng-content></ng-content>
+    </div>
+</form>
diff --git a/client/src/app/admin/instance/webpage/components/webpage-form.component.ts b/client/src/app/admin/instance/webpage/components/webpage-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e473ea5338604cc3fc87b8ee7d71d4e72b99ef58
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/components/webpage-form.component.ts
@@ -0,0 +1,51 @@
+import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
+
+import { Webpage, WebpageFamily } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-form',
+    templateUrl: 'webpage-form.component.html'
+})
+export class WebpageFormComponent implements OnInit {
+    @Input() webpage: Webpage;
+    @Input() webpageFamilyList: WebpageFamily[];
+    @Input() idWebpageFamily: number;
+    @Output() onSubmit: EventEmitter<Webpage> = new EventEmitter();
+
+    public form = new FormGroup({
+        label: new FormControl('', [Validators.required]),
+        icon: new FormControl(null),
+        display: new FormControl('', [Validators.required]),
+        title: new FormControl('', [Validators.required]),
+        content: new FormControl('', [Validators.required]),
+        id_webpage_family: new FormControl('', [Validators.required])
+    });
+
+    ngOnInit() {
+        if (this.webpage) {
+            this.form.patchValue(this.webpage);
+        }
+        if (this.idWebpageFamily) {
+            this.form.controls.id_webpage_family.setValue(this.idWebpageFamily);
+        }
+    }
+
+    submit() {
+        if (this.webpage) {
+            this.onSubmit.emit({
+                ...this.webpage,
+                ...this.form.getRawValue()
+            });
+        } else {
+            this.onSubmit.emit(this.form.getRawValue());
+        }
+    }
+
+    getEditorConfig() {
+        return {
+            plugins: "lists link image table code help wordcount media",
+            content_css: "https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css"
+        };
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/containers/edit-webpage.component.html b/client/src/app/admin/instance/webpage/containers/edit-webpage.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..bafa14c209f1216f7fe95ffcc803ab0bf770a75d
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/containers/edit-webpage.component.html
@@ -0,0 +1,44 @@
+<app-spinner *ngIf="webpageListIsLoading | async"></app-spinner>
+
+<div *ngIf="webpageListIsLoaded | async" class="container-fluid">
+    <nav aria-label="breadcrumb">
+        <ol class="breadcrumb">
+            <li class="breadcrumb-item">
+                <a routerLink="/admin/instance/instance-list">
+                    Instances
+                </a>
+            </li>
+            <li class="breadcrumb-item">
+                <a routerLink="/admin/instance/configure-instance/{{ instanceName | async }}">
+                    Configure instance {{ instanceName | async }}
+                </a>
+            </li>
+            <li class="breadcrumb-item active">
+                <a routerLink="/admin/instance/configure-instance/{{ instanceName | async }}/webpage">
+                    Edit instance webpages
+                </a>
+            </li>
+            <li class="breadcrumb-item active" aria-current="page">Edit webpage {{ (webpage | async).label }}</li>
+        </ol>
+    </nav>
+</div>
+
+<div class="container">
+    <app-spinner *ngIf="(webpageListIsLoading | async) || (webpageFamilyListIsLoading | async)"></app-spinner>
+
+    <div *ngIf="(webpageListIsLoaded | async) && (webpageFamilyListIsLoaded | async)" class="row">
+        <div class="col-12">
+            <app-webpage-form 
+                [webpage]="webpage | async"
+                [webpageFamilyList]="webpageFamilyList | async"
+                (onSubmit)="editWebpage($event)"
+                #formWebpage>
+                <button [disabled]="!formWebpage.form.valid || formWebpage.form.pristine" type="submit" class="btn btn-primary">
+                    <span class="fa fa-database"></span> Edit webpage
+                </button>
+                &nbsp;
+                <a routerLink="/admin/instance/configure-instance/{{ instanceName | async }}/webpage" class="btn btn-danger">Cancel</a>
+            </app-webpage-form>
+        </div>
+    </div>
+</div>
diff --git a/client/src/app/admin/instance/webpage/containers/edit-webpage.component.ts b/client/src/app/admin/instance/webpage/containers/edit-webpage.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7fe0d9426a4dcc27dee4acdd9892a5d49b48b457
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/containers/edit-webpage.component.ts
@@ -0,0 +1,47 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component } from '@angular/core';
+
+import { Observable } from 'rxjs';
+import { Store } from '@ngrx/store';
+
+import { WebpageFamily, Webpage } from 'src/app/metamodel/models';
+import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
+import * as webpageSelector from 'src/app/metamodel/selectors/webpage.selector';
+import * as webpageFamilySelector from 'src/app/metamodel/selectors/webpage-family.selector';
+
+@Component({
+    selector: 'app-edit-webpage',
+    templateUrl: 'edit-webpage.component.html'
+})
+export class EditWebpageComponent {
+    public instanceName: Observable<string>;
+    public webpageListIsLoading: Observable<boolean>;
+    public webpageListIsLoaded: Observable<boolean>;
+    public webpage: Observable<Webpage>;
+    public webpageFamilyListIsLoading: Observable<boolean>;
+    public webpageFamilyListIsLoaded: Observable<boolean>;
+    public webpageFamilyList: Observable<WebpageFamily[]>;
+
+    constructor(private store: Store<{ }>) {
+        this.instanceName = this.store.select(instanceSelector.selectInstanceNameByRoute);
+        this.webpageListIsLoading = store.select(webpageSelector.selectWebpageListIsLoading);
+        this.webpageListIsLoaded = store.select(webpageSelector.selectWebpageListIsLoaded);
+        this.webpage = this.store.select(webpageSelector.selectWebpageByRouteId);
+        this.webpageFamilyListIsLoading = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoading);
+        this.webpageFamilyListIsLoaded = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoaded);
+        this.webpageFamilyList = store.select(webpageFamilySelector.selectAllWebpageFamilies);
+    }
+
+    editWebpage(webpage: Webpage) {
+        this.store.dispatch(webpageActions.editWebpage({ webpage }));
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/containers/new-webpage.component.html b/client/src/app/admin/instance/webpage/containers/new-webpage.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..26af60c6ccd93d8f4ae85942a5a7a795f35ce3e4
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/containers/new-webpage.component.html
@@ -0,0 +1,42 @@
+<div class="container-fluid">
+    <nav aria-label="breadcrumb">
+        <ol class="breadcrumb">
+            <li class="breadcrumb-item">
+                <a routerLink="/admin/instance/instance-list">
+                    Instances
+                </a>
+            </li>
+            <li class="breadcrumb-item">
+                <a routerLink="/admin/instance/configure-instance/{{ instanceName | async }}">
+                    Configure instance {{ instanceName | async }}
+                </a>
+            </li>
+            <li class="breadcrumb-item active">
+                <a routerLink="/admin/instance/configure-instance/{{ instanceName | async }}/webpage">
+                    Edit instance webpages
+                </a>
+            </li>
+            <li class="breadcrumb-item active" aria-current="page">New webpage</li>
+        </ol>
+    </nav>
+</div>
+
+<div class="container">
+    <app-spinner *ngIf="webpageFamilyListIsLoading | async"></app-spinner>
+    
+    <div *ngIf="webpageFamilyListIsLoaded | async" class="row">
+        <div class="col-12">
+            <app-webpage-form 
+                [webpageFamilyList]="webpageFamilyList | async"
+                [idWebpageFamily]="idWebpageFamily | async"
+                (onSubmit)="addNewWebpage($event)" 
+                #formWebpage>
+                <button [disabled]="!formWebpage.form.valid || formWebpage.form.pristine" type="submit" class="btn btn-primary">
+                    <span class="fa fa-database"></span> Add the new webpage
+                </button>
+                &nbsp;
+                <a routerLink="/admin/instance/configure-instance/{{ instanceName | async }}/webpage" class="btn btn-danger">Cancel</a>
+            </app-webpage-form>
+        </div>
+    </div>
+</div>
diff --git a/client/src/app/admin/instance/webpage/containers/new-webpage.component.ts b/client/src/app/admin/instance/webpage/containers/new-webpage.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7e1aec308799fd42937c050ed357daea9e3265fe
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/containers/new-webpage.component.ts
@@ -0,0 +1,40 @@
+import { Component } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+import { Store } from '@ngrx/store';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { WebpageFamily, Webpage } from 'src/app/metamodel/models';
+import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
+import * as webpageFamilySelector from 'src/app/metamodel/selectors/webpage-family.selector';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
+
+@Component({
+    selector: 'app-new-webpage',
+    templateUrl: 'new-webpage.component.html'
+})
+export class NewWebpageComponent {
+    public instanceName: Observable<string>;
+    public webpageFamilyListIsLoading: Observable<boolean>;
+    public webpageFamilyListIsLoaded: Observable<boolean>;
+    public webpageFamilyList: Observable<WebpageFamily[]>;
+    public idWebpageFamily: Observable<number>;
+
+    constructor(private store: Store<{ }>, private route: ActivatedRoute) {
+        this.instanceName = this.store.select(instanceSelector.selectInstanceNameByRoute);
+        this.webpageFamilyListIsLoading = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoading);
+        this.webpageFamilyListIsLoaded = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoaded);
+        this.webpageFamilyList = store.select(webpageFamilySelector.selectAllWebpageFamilies);
+    }
+
+    ngOnInit() {
+        this.idWebpageFamily = this.route.queryParamMap.pipe(
+            map(params => +params.get('id_webpage_family'))
+        );
+    }
+
+    addNewWebpage(webpage: Webpage) {
+        this.store.dispatch(webpageActions.addWebpage({ webpage }));
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/containers/webpage-list.component.html b/client/src/app/admin/instance/webpage/containers/webpage-list.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e84042daac26e2eff5383147142a4a82fa4f12e0
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/containers/webpage-list.component.html
@@ -0,0 +1,36 @@
+<div class="container-fluid">
+    <nav aria-label="breadcrumb">
+        <ol class="breadcrumb">
+            <li class="breadcrumb-item">
+                <a routerLink="/admin/instance/instance-list">
+                    Instances
+                </a>
+            </li>
+            <li class="breadcrumb-item">
+                <a routerLink="/admin/instance/configure-instance/{{ instanceName | async }}">
+                    Configure instance {{ instanceName | async }}
+                </a>
+            </li>
+            <li class="breadcrumb-item active" aria-current="page">Edit instance webpages</li>
+        </ol>
+    </nav>
+</div>
+
+<div class="container">
+    <app-spinner *ngIf="(webpageFamilyListIsLoading | async) || (webpageListIsLoading | async)"></app-spinner>
+
+    <ng-container *ngIf="(webpageFamilyListIsLoaded | async) && (webpageListIsLoaded | async)">
+        <app-webpage-buttons
+            (addWebpageFamily)="addWebpageFamily($event)">
+        </app-webpage-buttons>
+
+        <app-webpage-family-card 
+            *ngFor="let webpageFamily of (webpageFamilyList | async)"
+            [webpageFamily]="webpageFamily"
+            [webpageList]="webpageList | async"
+            (editWebpageFamily)="editWebpageFamily($event)"
+            (deleteWebpageFamily)="deleteWebpageFamily($event)"
+            (deleteWebpage)="deleteWebpage($event)">
+        </app-webpage-family-card>
+    </ng-container>
+</div>
diff --git a/client/src/app/admin/instance/webpage/containers/webpage-list.component.scss b/client/src/app/admin/instance/webpage/containers/webpage-list.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..0ac62a8554a7e9c2218b358116ba288d7ec9e8d4
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/containers/webpage-list.component.scss
@@ -0,0 +1,19 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+.card-add {
+    height: 200px;
+    background-color: #A8C96E;
+    transition: font-size 0.3s;
+}
+
+.card-add:hover {
+    background-color: #9dc25b;
+    font-size: 20px;
+}
diff --git a/client/src/app/admin/instance/webpage/containers/webpage-list.component.ts b/client/src/app/admin/instance/webpage/containers/webpage-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3b1fa3bda6ff869e4cb3ddc0e9b31fa07a170ba6
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/containers/webpage-list.component.ts
@@ -0,0 +1,61 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component } from '@angular/core';
+
+import { Observable } from 'rxjs';
+import { Store } from '@ngrx/store';
+
+import { WebpageFamily, Webpage } from 'src/app/metamodel/models';
+import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
+import * as webpageFamilyActions from 'src/app/metamodel/actions/webpage-family.actions';
+import * as webpageFamilySelector from 'src/app/metamodel/selectors/webpage-family.selector';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
+import * as webpageSelector from 'src/app/metamodel/selectors/webpage.selector';
+
+@Component({
+    selector: 'app-webpage-list',
+    templateUrl: 'webpage-list.component.html',
+    styleUrls: ['webpage-list.component.scss']
+})
+export class WebpageListComponent {
+    public instanceName: Observable<string>;
+    public webpageFamilyListIsLoading: Observable<boolean>;
+    public webpageFamilyListIsLoaded: Observable<boolean>;
+    public webpageFamilyList: Observable<WebpageFamily[]>;
+    public webpageListIsLoading: Observable<boolean>;
+    public webpageListIsLoaded: Observable<boolean>;
+    public webpageList: Observable<Webpage[]>;
+
+    constructor(private store: Store<{ }>) {
+        this.instanceName = this.store.select(instanceSelector.selectInstanceNameByRoute);
+        this.webpageFamilyListIsLoading = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoading);
+        this.webpageFamilyListIsLoaded = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoaded);
+        this.webpageFamilyList = store.select(webpageFamilySelector.selectAllWebpageFamilies);
+        this.webpageListIsLoading = store.select(webpageSelector.selectWebpageListIsLoading);
+        this.webpageListIsLoaded = store.select(webpageSelector.selectWebpageListIsLoaded);
+        this.webpageList = store.select(webpageSelector.selectAllWebpages);
+    }
+
+    addWebpageFamily(webpageFamily: WebpageFamily) {
+        this.store.dispatch(webpageFamilyActions.addWebpageFamily({ webpageFamily }));
+    }
+
+    editWebpageFamily(webpageFamily: WebpageFamily) {
+        this.store.dispatch(webpageFamilyActions.editWebpageFamily({ webpageFamily }));
+    }
+
+    deleteWebpageFamily(webpageFamily: WebpageFamily) {
+        this.store.dispatch(webpageFamilyActions.deleteWebpageFamily({ webpageFamily }));
+    }
+
+    deleteWebpage(webpage: Webpage) {
+        this.store.dispatch(webpageActions.deleteWebpage({ webpage }));
+    }
+}
diff --git a/client/src/app/admin/instance/webpage/webpage-routing.module.ts b/client/src/app/admin/instance/webpage/webpage-routing.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c162b80f521725e4a5e7ce25ebbb18c687d56fed
--- /dev/null
+++ b/client/src/app/admin/instance/webpage/webpage-routing.module.ts
@@ -0,0 +1,37 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { WebpageListComponent } from './containers/webpage-list.component';
+import { NewWebpageComponent } from './containers/new-webpage.component';
+import { EditWebpageComponent } from './containers/edit-webpage.component';
+
+const routes: Routes = [
+    { path: '', component: WebpageListComponent },
+    { path: 'new-webpage', component: NewWebpageComponent },
+    { path: 'edit-webpage/:id', component: EditWebpageComponent }
+];
+
+/**
+ * @class
+ * @classdesc Dataset routing module.
+ */
+@NgModule({
+    imports: [RouterModule.forChild(routes)],
+    exports: [RouterModule]
+})
+export class WebpageRoutingModule { }
+
+export const routedComponents = [
+    WebpageListComponent,
+    NewWebpageComponent,
+    EditWebpageComponent
+];
diff --git a/client/src/app/instance/home/home.module.ts b/client/src/app/admin/instance/webpage/webpage.module.ts
similarity index 53%
rename from client/src/app/instance/home/home.module.ts
rename to client/src/app/admin/instance/webpage/webpage.module.ts
index 0859f9d1d45bb11bc68c64553fafe1b5961d8ef3..fa6df84f0686a88d147306cdd9fb6ee3752d8f68 100644
--- a/client/src/app/instance/home/home.module.ts
+++ b/client/src/app/admin/instance/webpage/webpage.module.ts
@@ -9,22 +9,30 @@
 
 import { NgModule } from '@angular/core';
 
+import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
+
 import { SharedModule } from 'src/app/shared/shared.module';
-import { HomeRoutingModule, routedComponents } from './home-routing.module';
+import { WebpageRoutingModule, routedComponents } from './webpage-routing.module';
 import { dummiesComponents } from './components';
+import { AdminSharedModule } from '../../admin-shared/admin-shared.module';
 
 /**
  * @class
- * @classdesc Home module.
+ * @classdesc Dataset module.
  */
 @NgModule({
     imports: [
         SharedModule,
-        HomeRoutingModule
+        WebpageRoutingModule,
+        AdminSharedModule,
+        EditorModule
     ],
     declarations: [
         routedComponents,
         dummiesComponents
+    ],
+    providers: [
+        { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }
     ]
 })
-export class HomeModule { }
+export class WebpageModule { }
diff --git a/client/src/app/instance/components/index.ts b/client/src/app/instance/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..74d75b58fafa3e513a25c4bc99b1a732210378b8
--- /dev/null
+++ b/client/src/app/instance/components/index.ts
@@ -0,0 +1,18 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { InstanceNavbarComponent } from './instance-navbar.component';
+import { WebpageFamilyNavComponent } from './webpage-family-nav.component';
+import { WebpageFamilyNavMobileComponent } from './webpage-family-nav-mobile.component';
+
+export const dummiesComponents = [
+    InstanceNavbarComponent,
+    WebpageFamilyNavComponent,
+    WebpageFamilyNavMobileComponent
+];
diff --git a/client/src/app/instance/components/instance-navbar.component.html b/client/src/app/instance/components/instance-navbar.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..aa363e918416c11f084d6389b9c79af751c4c6e4
--- /dev/null
+++ b/client/src/app/instance/components/instance-navbar.component.html
@@ -0,0 +1,147 @@
+<nav class="navbar navbar-light bg-light navbar-expand-md fixed-top border-bottom">
+    <!-- Logo -->
+    <a [routerLink]="getInstanceBaseHref()" class="navbar-brand">
+        <img *ngIf="instance.public" [src]="getLogoHref()" alt="Instance logo" />
+        <img *ngIf="!instance.public" [src]="getLogoHref() | authImage | async" alt="Instance logo" />
+    </a>
+
+    <!-- Navigation -->
+    <div class="collapse navbar-collapse" id="navbarCollapse">
+        <ul class="navbar-nav mr-auto">
+            <app-webpage-family-nav
+                *ngFor="let webpageFamily of webpageFamilyList"
+                [webpageFamily]="webpageFamily"
+                [webpageList]="getWebpageListByFamily(webpageFamily)">
+            </app-webpage-family-nav>
+            <li *ngIf="instance.search_by_criteria_allowed" class="nav-item pr-3">
+                <a class="nav-link" routerLink="search" routerLinkActive="active">
+                    <span class="fas fa-search"></span> {{ instance.search_by_criteria_label }}
+                </a>
+            </li>
+            <li *ngIf="instance.search_multiple_allowed" class="nav-item pr-3">
+                <a class="nav-link" routerLink="search-multiple" routerLinkActive="active">
+                    <span class="fas fa-search-plus"></span> {{ instance.search_multiple_label }}
+                </a>
+            </li>
+            <li *ngIf="instance.documentation_allowed" class="nav-item pr-3">
+                <a class="nav-link" routerLink="documentation" routerLinkActive="active">
+                    <span class="fas fa-question"></span> {{ instance.documentation_label }}
+                </a>
+            </li>
+        </ul>
+
+        <ul class="navbar-nav justify-content-end">
+            <li class="nav-item pr-3">
+                <a *ngIf="instance.back_to_portal" class="nav-link" routerLink="/portal" routerLinkActive="active">
+                    <span class="fa-solid fa-right-to-bracket"></span> Back to portal
+                </a>
+            </li>
+            <li *ngIf="!authenticationEnabled" class="nav-item pr-3">
+                <a class="nav-link" routerLink="/admin" routerLinkActive="active">
+                    <span class="fas fa-tools"></span> Admin
+                </a>
+            </li>
+        </ul>
+        <!-- sign in / sign out -->
+        <button *ngIf="authenticationEnabled && !isAuthenticated"
+            class="btn btn-outline-success my-2 my-sm-0"
+            id="button-sign-in"
+            (click)="login.emit()">
+            Sign In / Register
+        </button>
+        <span *ngIf="isAuthenticated" id="dropdown-menu" dropdown>
+            <button id="button-basic" dropdownToggle type="button" class="btn btn-light" aria-controls="dropdown-basic">
+                <span class="fa-stack" [ngStyle]="{ color: instance.design_color }">
+                    <span class="fas fa-circle fa-2x"></span>
+                    <span class="fas fa-user fa-stack-1x fa-inverse"></span>
+                </span>
+                &nbsp;
+                <span class="fas fa-chevron-down text-secondary"></span>
+            </button>
+            <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu" aria-labelledby="basic-link">
+                <li id="li-email" role="menuitem">
+                    <span class="dropdown-item font-italic">{{ userProfile.email }}</span>
+                </li>
+                <li class="divider dropdown-divider"></li>
+                <li role="menuitem">
+                    <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin">
+                        <span class="fas fa-tools"></span> Admin
+                    </a>
+                    <a class="dropdown-item pointer" (click)="openEditProfile.emit()">
+                        <span class="fas fa-id-card"></span> Edit profile
+                    </a>
+                </li>
+                <li class="divider dropdown-divider"></li>
+                <li role="menuitem">
+                    <a class="dropdown-item text-danger pointer" (click)="logout.emit()">
+                        <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out
+                    </a>
+                </li>
+            </ul>
+        </span>
+    </div>
+
+    <!-- Navigation Mobile -->
+    <span dropdown>
+        <button id="button-basic" dropdownToggle type="button" class="navbar-toggler" aria-controls="dropdown-basic">
+            <span class="fas fa-bars"></span>
+        </button>
+        <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu"
+            aria-labelledby="basic-link">
+            <li *ngIf="isAuthenticated" role="menuitem">
+                <span class="dropdown-item font-italic">{{ userProfile.email }}</span>
+            </li>
+            <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li>
+            <app-webpage-family-nav-mobile
+                *ngFor="let webpageFamily of webpageFamilyList"
+                [webpageFamily]="webpageFamily"
+                [webpageList]="getWebpageListByFamily(webpageFamily)">
+            </app-webpage-family-nav-mobile>
+            <li *ngIf="instance.search_by_criteria_allowed" role="menuitem">
+                <a class="dropdown-item" routerLink="search">
+                    <span class="fas fa-search fa-fw"></span> {{ instance.search_by_criteria_label }}
+                </a>
+            </li>
+            <li *ngIf="instance.search_multiple_allowed" role="menuitem">
+                <a class="dropdown-item" routerLink="search-multiple">
+                    <span class="fas fa-search-plus fa-fw"></span> {{ instance.search_multiple_label }}
+                </a>
+            </li>
+            <li *ngIf="instance.documentation_allowed" role="menuitem">
+                <a class="dropdown-item" routerLink="documentation">
+                    <span class="fas fa-question fa-fw"></span> {{ instance.documentation_label }}
+                </a>
+            </li>
+            <li role="menuitem">
+                <a *ngIf="instance.back_to_portal" class="dropdown-item" routerLink="/portal">
+                    <span class="fa-solid fa-right-to-bracket fa-fw"></span> Back to portal
+                </a>
+            </li>
+            <li *ngIf="isAuthenticated || !authenticationEnabled" class="divider dropdown-divider"></li>
+            <li *ngIf="!authenticationEnabled" role="menuitem">
+                <a class="dropdown-item pointer" routerLink="/admin">
+                    <span class="fas fa-tools"></span> Admin
+                </a>
+            </li>
+            <li *ngIf="isAuthenticated" role="menuitem">
+                <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin">
+                    <span class="fas fa-tools"></span> Admin
+                </a>
+                <a class="dropdown-item pointer" (click)="openEditProfile.emit()">
+                    <span class="fas fa-id-card"></span> Edit profile
+                </a>
+            </li>
+            <li *ngIf="authenticationEnabled" class="divider dropdown-divider"></li>
+            <li *ngIf="authenticationEnabled && !isAuthenticated" role="menuitem">
+                <a class="dropdown-item pointer text-success" (click)="login.emit()">
+                    <span class="fas fa-sign-in-alt fa-fw"></span> Sign In / Register
+                </a>
+            </li>
+            <li *ngIf="isAuthenticated" role="menuitem">
+                <a class="dropdown-item pointer text-danger" (click)="logout.emit()">
+                    <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out
+                </a>
+            </li>
+        </ul>
+    </span>
+</nav>
diff --git a/client/src/app/instance/home/components/welcome.component.scss b/client/src/app/instance/components/instance-navbar.component.scss
similarity index 72%
rename from client/src/app/instance/home/components/welcome.component.scss
rename to client/src/app/instance/components/instance-navbar.component.scss
index 0c0f1a8992260f501b2473c9270dcaa856ca8654..1cc4ce8a2b777fb65944b30cbb3363caf453b52f 100644
--- a/client/src/app/instance/home/components/welcome.component.scss
+++ b/client/src/app/instance/components/instance-navbar.component.scss
@@ -7,11 +7,11 @@
  * file that was distributed with this source code.
  */
 
-div.jumbotron p {
-    line-height: 35px;
+.dropdown-up {
+    top: 80% !important;
+    right: 5px !important;
 }
 
-.img-fluid {
-    height: auto;
-    max-width: 100%;
-}
\ No newline at end of file
+img {
+    height: 60px;
+}
diff --git a/client/src/app/shared/components/navbar.component.ts b/client/src/app/instance/components/instance-navbar.component.ts
similarity index 57%
rename from client/src/app/shared/components/navbar.component.ts
rename to client/src/app/instance/components/instance-navbar.component.ts
index 18f9cba7087998a49e403ff6f56ec91ecf13228b..d40451e0321799228ff10fb70514e5ebff4e2996 100644
--- a/client/src/app/shared/components/navbar.component.ts
+++ b/client/src/app/instance/components/instance-navbar.component.ts
@@ -1,39 +1,26 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
+import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core';
 
-import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
-
-import { Instance } from 'src/app/metamodel/models';
+import { Instance, WebpageFamily, Webpage } from 'src/app/metamodel/models';
 import { UserProfile } from 'src/app/auth/user-profile.model';
 import { isAdmin } from 'src/app/shared/utils';
 
-/**
- * @class
- * @classdesc Navbar component.
- */
 @Component({
-    selector: 'app-navbar',
-    templateUrl: 'navbar.component.html',
-    styleUrls: [ 'navbar.component.scss' ],
+    selector: 'app-instance-navbar',
+    templateUrl: 'instance-navbar.component.html',
+    styleUrls: [ 'instance-navbar.component.scss' ],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class NavbarComponent {
-    @Input() links: {label: string, icon: string, routerLink: string}[];
+export class InstanceNavbarComponent {
     @Input() isAuthenticated: boolean;
     @Input() userProfile: UserProfile = null;
     @Input() userRoles: string[];
-    @Input() baseHref: string;
     @Input() authenticationEnabled: boolean;
     @Input() apiUrl: string;
     @Input() adminRoles: string[];
-    @Input() url: string;
     @Input() instance: Instance;
+    @Input() webpageFamilyList: WebpageFamily[];
+    @Input() webpageList: Webpage[];
+    @Input() firstWebpage: Webpage;
     @Output() login: EventEmitter<any> = new EventEmitter();
     @Output() logout: EventEmitter<any> = new EventEmitter();
     @Output() openEditProfile: EventEmitter<any> = new EventEmitter();
@@ -47,14 +34,6 @@ export class NavbarComponent {
         return isAdmin(this.adminRoles, this.userRoles);
     }
 
-    isPortalRoute() {
-        return this.url.includes('portal');
-    }
-
-    isAdminRoute() {
-        return this.url.includes('admin');
-    }
-
     /**
      * Returns logo href.
      *
@@ -66,4 +45,16 @@ export class NavbarComponent {
         }
         return 'assets/cesam_anis40.png';
     }
+
+    getInstanceBaseHref() {
+        if (this.firstWebpage) {
+            return `/instance/${this.instance.name}/webpage/${this.firstWebpage.id}`;
+        } else {
+            return `/instance/${this.instance.name}`;
+        }
+    }
+
+    getWebpageListByFamily(webpageFamily) {
+        return this.webpageList.filter(webpage => webpage.id_webpage_family === webpageFamily.id);
+    }
 }
diff --git a/client/src/app/instance/components/webpage-family-nav-mobile.component.html b/client/src/app/instance/components/webpage-family-nav-mobile.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..d95db1eee897dfe7a5e768753a30122be3fa683f
--- /dev/null
+++ b/client/src/app/instance/components/webpage-family-nav-mobile.component.html
@@ -0,0 +1,17 @@
+<li *ngIf="webpageList.length === 1" role="menuitem">
+    <a class="dropdown-item" routerLink="webpage/{{ webpageList[0].id }}">
+        <span *ngIf="webpageList[0].icon" [ngClass]="webpageList[0].icon"></span> {{ webpageList[0].label }}
+    </a>
+</li>
+
+<ng-container *ngIf="webpageList.length > 1">
+    <li class="dropdown-item font-weight-bold">
+        <span *ngIf="webpageFamily.icon" [ngClass]="webpageFamily.icon"></span> {{ webpageFamily.label }} :
+    </li>
+    <li *ngFor="let webpage of webpageList" role="menuitem" class="ml-2">
+        <a class="dropdown-item" routerLink="webpage/{{ webpage.id }}" fragment="nested-dropdowns">
+            <span *ngIf="webpage.icon" [ngClass]="webpage.icon"></span> {{ webpage.label }}
+        </a>
+    </li>
+    <li class="divider dropdown-divider"></li>
+</ng-container>
diff --git a/client/src/app/instance/components/webpage-family-nav-mobile.component.ts b/client/src/app/instance/components/webpage-family-nav-mobile.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e43ce2fd0786eda8889535bfe5e5d7020414d424
--- /dev/null
+++ b/client/src/app/instance/components/webpage-family-nav-mobile.component.ts
@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core';
+
+import { WebpageFamily, Webpage } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-family-nav-mobile',
+    templateUrl: 'webpage-family-nav-mobile.component.html'
+})
+export class WebpageFamilyNavMobileComponent {
+    @Input() webpageFamily: WebpageFamily;
+    @Input() webpageList: Webpage[];
+}
diff --git a/client/src/app/instance/components/webpage-family-nav.component.html b/client/src/app/instance/components/webpage-family-nav.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..d01574033a559d254ce33721545abcd59cfafa77
--- /dev/null
+++ b/client/src/app/instance/components/webpage-family-nav.component.html
@@ -0,0 +1,16 @@
+<li *ngIf="webpageList.length === 1" class="nav-item pr-3">
+    <a class="nav-link" routerLink="webpage/{{ webpageList[0].id }}" routerLinkActive="active">
+        <span *ngIf="webpageList[0].icon" [ngClass]="webpageList[0].icon"></span> {{ webpageList[0].label }}
+    </a>
+</li>
+
+<li *ngIf="webpageList.length > 1" dropdown class="nav-item dropdown pr-3">
+    <a class="nav-link dropdown-toggle" dropdownToggle data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
+        <span *ngIf="webpageFamily.icon" [ngClass]="webpageFamily.icon"></span> {{ webpageFamily.label }}
+    </a>
+    <div *dropdownMenu class="dropdown-menu">
+        <a *ngFor="let webpage of webpageList" class="dropdown-item" routerLink="webpage/{{ webpage.id }}" routerLinkActive="active">
+            <span *ngIf="webpage.icon" [ngClass]="webpage.icon"></span> {{ webpage.label }}
+        </a>
+    </div>
+</li>
diff --git a/client/src/app/instance/components/webpage-family-nav.component.ts b/client/src/app/instance/components/webpage-family-nav.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c9164d0c1fb2d2fada0809df6de7df718cf4110e
--- /dev/null
+++ b/client/src/app/instance/components/webpage-family-nav.component.ts
@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core';
+
+import { WebpageFamily, Webpage } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-family-nav',
+    templateUrl: 'webpage-family-nav.component.html'
+})
+export class WebpageFamilyNavComponent {
+    @Input() webpageFamily: WebpageFamily;
+    @Input() webpageList: Webpage[];
+}
diff --git a/client/src/app/instance/home/components/index.ts b/client/src/app/instance/home/components/index.ts
deleted file mode 100644
index 8973b374e7bfb09e0ded7e6230d0170a6e266273..0000000000000000000000000000000000000000
--- a/client/src/app/instance/home/components/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { WelcomeComponent } from './welcome.component';
-
-export const dummiesComponents = [
-    WelcomeComponent
-];
diff --git a/client/src/app/instance/home/components/welcome.component.html b/client/src/app/instance/home/components/welcome.component.html
deleted file mode 100644
index f59854e39456e2bbbdb7dcaf1fcf902139b706ef..0000000000000000000000000000000000000000
--- a/client/src/app/instance/home/components/welcome.component.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<div class="row align-items-center jumbotron">
-    <div class="col-6 col-md-4 order-md-2 mx-auto text-center">
-        <img *ngIf="instance.public" class="img-fluid mb-3 mb-md-0" [src]="getLogoSrc()" alt="Instance logo">
-        <img *ngIf="!instance.public" class="img-fluid mb-3 mb-md-0" [src]="getLogoSrc() | authImage | async" alt="Instance logo">
-    </div>
-    <div class="col-md-8 order-md-1 text-justify pr-md-5" [innerHtml]="instance.home_component_config.home_component_text"></div>
-</div>
diff --git a/client/src/app/instance/home/components/welcome.component.spec.ts b/client/src/app/instance/home/components/welcome.component.spec.ts
deleted file mode 100644
index 71b0e6306b44b4b6fdbbdaee2feaf0d98e0a4265..0000000000000000000000000000000000000000
--- a/client/src/app/instance/home/components/welcome.component.spec.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { TestBed, waitForAsync, ComponentFixture  } from '@angular/core/testing';
-import { RouterTestingModule } from '@angular/router/testing';
-
-import { WelcomeComponent } from './welcome.component';
-
-describe('[Instance][Home][Component] WelcomeComponent', () => {
-    let component: WelcomeComponent;
-    let fixture: ComponentFixture<WelcomeComponent>;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            imports: [RouterTestingModule],
-            declarations: [WelcomeComponent]
-        }).compileComponents();
-        fixture = TestBed.createComponent(WelcomeComponent);
-        component = fixture.componentInstance;
-    }));
-
-    it('should create the component', () => {
-        expect(component).toBeDefined();
-    });
-
-    it('#getLogoSrc() should return logo URL address', () => {
-        component.apiUrl = 'http://test.com';
-        component.instance = {
-            name: 'myInstance',
-            label: 'My Instance',
-            description: 'My Instance description',
-            scientific_manager: 'M. Dupont',
-            instrument: 'Multiple',
-            wavelength_domain: 'Visible',
-            display: 10,
-            data_path: 'data/path',
-            files_path: 'files',
-            public: true,
-            portal_logo: 'logo.png',
-            design_color: 'green',
-            design_background_color: 'darker green',
-            design_logo: '/path/to/logo',
-            design_favicon: '/path/to/favicon',
-            home_component: 'HomeComponent',
-            home_component_config: {
-                home_component_text: 'Description',
-                home_component_logo: '/path/to/logo'
-            },
-            samp_enabled: true,
-            back_to_portal: true,
-            search_by_criteria_allowed: true,
-            search_by_criteria_label: 'Search',
-            search_multiple_allowed: true,
-            search_multiple_label: 'Search multiple',
-            search_multiple_all_datasets_selected: true,
-            documentation_allowed: true,
-            documentation_label: 'Documentation',
-            nb_dataset_families: 1,
-            nb_datasets: 2
-        };
-        expect(component.getLogoSrc()).toBe('http://test.com/instance/myInstance/file-explorer/path/to/logo');
-    });
-});
diff --git a/client/src/app/instance/home/components/welcome.component.ts b/client/src/app/instance/home/components/welcome.component.ts
deleted file mode 100644
index 316ec2b11af2cc919bcd80f868cc89184e7feee4..0000000000000000000000000000000000000000
--- a/client/src/app/instance/home/components/welcome.component.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { Component, Input } from '@angular/core';
-
-import { Instance } from 'src/app/metamodel/models';
-
-/**
- * @class
- * @classdesc Welcome component.
- */
-@Component({
-    selector: 'app-welcome',
-    styleUrls: ['welcome.component.scss'],
-    templateUrl: 'welcome.component.html'
-})
-export class WelcomeComponent {
-    @Input() instance: Instance;
-    @Input() apiUrl: string;
-
-    /**
-     * Returns the logo url.
-     *
-     * @return string
-     */
-    getLogoSrc(): string {
-        return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.home_component_config.home_component_logo}`;
-    }
-}
diff --git a/client/src/app/instance/home/home.component.html b/client/src/app/instance/home/home.component.html
deleted file mode 100644
index 96b9457fbe7b3a811f4c17a53931bf9fe3ab32ce..0000000000000000000000000000000000000000
--- a/client/src/app/instance/home/home.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="container">
-    <app-welcome [instance]="instance | async" [apiUrl]="getApiUrl()"></app-welcome>
-</div>
diff --git a/client/src/app/instance/home/home.component.spec.ts b/client/src/app/instance/home/home.component.spec.ts
deleted file mode 100644
index a46d838444b3979df76b2a2286a91e1a7f952a13..0000000000000000000000000000000000000000
--- a/client/src/app/instance/home/home.component.spec.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { TestBed, waitForAsync, ComponentFixture  } from '@angular/core/testing';
-import { RouterTestingModule } from '@angular/router/testing';
-
-import { provideMockStore, MockStore } from '@ngrx/store/testing';
-
-import { HomeComponent } from './home.component';
-import { AppConfigService } from 'src/app/app-config.service';
-import { Instance } from '../../metamodel/models';
-
-describe('[Instance][Home] HomeComponent', () => {
-    @Component({ selector: '<app-welcome', template: '' })
-    class WelcomeStubComponent {
-        @Input() instance: Instance;
-        @Input() apiUrl: string;
-    }
-
-    let component: HomeComponent;
-    let fixture: ComponentFixture<HomeComponent>;
-    let store: MockStore;
-    let appConfigServiceStub = new AppConfigService();
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            imports: [RouterTestingModule],
-            declarations: [
-                HomeComponent,
-                WelcomeStubComponent
-            ],
-            providers: [
-                provideMockStore({ }),
-                { provide: AppConfigService, useValue: appConfigServiceStub },
-            ]
-        }).compileComponents();
-        fixture = TestBed.createComponent(HomeComponent);
-        component = fixture.componentInstance;
-        store = TestBed.inject(MockStore);
-    }));
-
-    it('should create the component', () => {
-        expect(component).toBeDefined();
-    });
-
-    it('#getApiUrl() should return API URL address', () => {
-        appConfigServiceStub.apiUrl = 'http://test.com';
-        expect(component.getApiUrl()).toBe('http://test.com');
-    });
-});
diff --git a/client/src/app/instance/home/home.component.ts b/client/src/app/instance/home/home.component.ts
deleted file mode 100644
index 48dbbd6fabd40abf96d3e72686d5caf20ae760ec..0000000000000000000000000000000000000000
--- a/client/src/app/instance/home/home.component.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { Component } from '@angular/core';
-
-import { Store } from '@ngrx/store';
-import { Observable } from 'rxjs';
-
-import { Instance } from 'src/app/metamodel/models';
-import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
-import { AppConfigService } from 'src/app/app-config.service';
-
-/**
- * @class
- * @classdesc Home component.
- */
-@Component({
-    selector: 'app-home',
-    templateUrl: 'home.component.html'
-})
-export class HomeComponent {
-    public instance: Observable<Instance>;
-
-    constructor(private store: Store<{ }>, private config: AppConfigService) {
-        this.instance = this.store.select(instanceSelector.selectInstanceByRouteName);
-    }
-
-    /**
-     * Returns API url.
-     *
-     * @return string
-     */
-    getApiUrl(): string {
-        return this.config.apiUrl;
-    }
-}
diff --git a/client/src/app/instance/instance-routing.module.ts b/client/src/app/instance/instance-routing.module.ts
index e7679bc2e7366856f410e83c92738806c23b3902..23bb9010329b3d0a78e4d364403291c40e27d181 100644
--- a/client/src/app/instance/instance-routing.module.ts
+++ b/client/src/app/instance/instance-routing.module.ts
@@ -16,8 +16,7 @@ import { InstanceAuthGuard } from './instance-auth.guard';
 const routes: Routes = [
     { 
         path: ':iname', component: InstanceComponent, canActivate: [InstanceAuthGuard], children: [
-            { path: '', redirectTo: 'home', pathMatch: 'full' },
-            { path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) },
+            { path: 'webpage', loadChildren: () => import('./webpage/webpage.module').then(m => m.WebpageModule) },
             { path: 'search', loadChildren: () => import('./search/search.module').then(m => m.SearchModule) },
             { path: 'search-multiple', loadChildren: () => import('./search-multiple/search-multiple.module').then(m => m.SearchMultipleModule) },
             { path: 'documentation', loadChildren: () => import('./documentation/documentation.module').then(m => m.DocumentationModule) }
diff --git a/client/src/app/instance/instance.component.html b/client/src/app/instance/instance.component.html
index e31861eec51d537e3f662d6054c19336ddd0c0da..b52a32734bc9f66aadae6bafcd4763b948c39254 100644
--- a/client/src/app/instance/instance.component.html
+++ b/client/src/app/instance/instance.component.html
@@ -1,20 +1,24 @@
-<header>
-    <app-navbar
-        [links]="links"
-        [isAuthenticated]="isAuthenticated | async"
-        [userProfile]="userProfile | async"
-        [userRoles]="userRoles | async"
-        [baseHref]="getBaseHref()"
-        [authenticationEnabled]="getAuthenticationEnabled()"
-        [adminRoles]="getAdminRoles()"
-        [url]="url | async"
-        [apiUrl]="getApiUrl()"
-        [instance]="instance | async"
-        (login)="login()"
-        (logout)="logout()"
-        (openEditProfile)="openEditProfile()">
-    </app-navbar>
-</header>
-<main role="main" class="container-fluid pb-4">
-    <router-outlet></router-outlet>
-</main>
+<app-spinner *ngIf="(webpageFamilyListIsLoading | async) || (webpageListIsLoading | async)"></app-spinner>
+
+<ng-container *ngIf="(webpageFamilyListIsLoaded | async) && (webpageListIsLoaded | async)">
+    <header>
+        <app-instance-navbar
+            [isAuthenticated]="isAuthenticated | async"
+            [userProfile]="userProfile | async"
+            [userRoles]="userRoles | async"
+            [authenticationEnabled]="getAuthenticationEnabled()"
+            [apiUrl]="getApiUrl()"
+            [adminRoles]="getAdminRoles()"
+            [instance]="instance | async"
+            [webpageFamilyList]="webpageFamilyList | async"
+            [webpageList]="webpageList | async"
+            [firstWebpage]="firstWebpage | async"
+            (login)="login()"
+            (logout)="logout()"
+            (openEditProfile)="openEditProfile()">
+        </app-instance-navbar>
+    </header>
+    <main role="main" class="container-fluid pb-4">
+        <router-outlet></router-outlet>
+    </main>
+</ng-container>
diff --git a/client/src/app/instance/instance.component.spec.ts b/client/src/app/instance/instance.component.spec.ts
index c501001a66bf2ae9024dc7a018370f1517957cc9..fc0f236554183fa3f49a5482da6ccf752528ae8c 100644
--- a/client/src/app/instance/instance.component.spec.ts
+++ b/client/src/app/instance/instance.component.spec.ts
@@ -9,22 +9,27 @@ import { of } from 'rxjs';
 import { InstanceComponent } from './instance.component';
 import { AppConfigService } from 'src/app/app-config.service';
 import * as authActions from 'src/app/auth/auth.actions';
-import { Instance } from '../metamodel/models';
+import { Instance, WebpageFamily, Webpage } from '../metamodel/models';
 import { UserProfile } from '../auth/user-profile.model';
 import * as datasetFamilyActions from '../metamodel/actions/dataset-family.actions';
 import * as datasetActions from '../metamodel/actions/dataset.actions';
 import * as datasetGroupActions from 'src/app/metamodel/actions/dataset-group.actions';
+import * as webpageFamilyActions from 'src/app/metamodel/actions/webpage-family.actions';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
 
 describe('[Instance] InstanceComponent', () => {
-    @Component({ selector: 'app-navbar', template: '' })
+    @Component({ selector: 'app-instance-navbar', template: '' })
     class NavbarStubComponent {
-        @Input() links: {label: string, icon: string, routerLink: string}[];
         @Input() isAuthenticated: boolean;
         @Input() userProfile: UserProfile = null;
-        @Input() baseHref: string;
+        @Input() userRoles: string[];
         @Input() authenticationEnabled: boolean;
         @Input() apiUrl: string;
+        @Input() adminRoles: string[];
         @Input() instance: Instance;
+        @Input() webpageFamilyList: WebpageFamily[] = null;
+        @Input() webpageList: Webpage[] = null;
+        @Input() firstWebpage: Webpage = null;
     }
 
     let component: InstanceComponent;
@@ -73,11 +78,6 @@ describe('[Instance] InstanceComponent', () => {
             design_background_color: 'darker green',
             design_logo: '/path/to/logo',
             design_favicon: '/path/to/favicon',
-            home_component: 'HomeComponent',
-            home_component_config: {
-                home_component_text: 'Description',
-                home_component_logo: '/path/to/logo'
-            },
             samp_enabled: true,
             back_to_portal: true,
             search_by_criteria_allowed: true,
@@ -90,32 +90,34 @@ describe('[Instance] InstanceComponent', () => {
             nb_dataset_families: 1,
             nb_datasets: 2
         };
+
+        const webpage: Webpage = {
+            id: 1,
+            label: 'Home',
+            title: 'Home',
+            display: 10,
+            icon: 'fas fa-home',
+            content: '<p>Hello</p>',
+            id_webpage_family: 1
+        }
+
         component.instance = of(instance);
+        component.firstWebpage = of(webpage);
         const spy = jest.spyOn(store, 'dispatch');
-        const expectedLinks = [
-            { label: 'Home', icon: 'fas fa-home', routerLink: 'home' },
-            { label: 'Search', icon: 'fas fa-search', routerLink: 'search' },
-            { label: 'Search multiple', icon: 'fas fa-search-plus', routerLink: 'search-multiple' },
-            { label: 'Documentation', icon: 'fas fa-question', routerLink: 'documentation' }
-        ];
         component.ngOnInit();
         Promise.resolve(null).then(function() {
-            expect(spy).toHaveBeenCalledTimes(3);
+            expect(spy).toHaveBeenCalledTimes(5);
             expect(spy).toHaveBeenCalledWith(datasetFamilyActions.loadDatasetFamilyList());
             expect(spy).toHaveBeenCalledWith(datasetActions.loadDatasetList());
             expect(spy).toHaveBeenCalledWith(datasetGroupActions.loadDatasetGroupList());
-            expect(component.links).toEqual(expectedLinks);
+            expect(spy).toHaveBeenCalledWith(webpageFamilyActions.loadWebpageFamilyList());
+            expect(spy).toHaveBeenCalledWith(webpageActions.loadWebpageList());
             expect(component.favIcon.href).toEqual('http://localhost/undefined/instance/myInstance/file-explorer/path/to/favicon');
             expect(component.title.textContent).toEqual('My Instance');
             done();
         });
     });
 
-    it('#getBaseHref() should return base href config key value', () => {
-        appConfigServiceStub.baseHref = '/my-project';
-        expect(component.getBaseHref()).toBe('/my-project');
-    });
-
     it('#authenticationEnabled() should return authentication enabled config key value', () => {
         appConfigServiceStub.authenticationEnabled = true;
         expect(component.getAuthenticationEnabled()).toBeTruthy();
diff --git a/client/src/app/instance/instance.component.ts b/client/src/app/instance/instance.component.ts
index dc692bd80d951ea5908d6529174138ef802a78ad..2545b07d695c853de90160a807de8beb334f5223 100644
--- a/client/src/app/instance/instance.component.ts
+++ b/client/src/app/instance/instance.component.ts
@@ -9,19 +9,23 @@
 
 import { Component, OnDestroy, OnInit } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
+import { ActivatedRoute, Router } from '@angular/router';
 
 import { Store } from '@ngrx/store';
 import { Observable, Subscription } from 'rxjs';
-import * as fromRouter from '@ngrx/router-store';
 
 import { UserProfile } from 'src/app/auth/user-profile.model';
-import { Instance } from 'src/app/metamodel/models';
+import { Instance, WebpageFamily, Webpage } from 'src/app/metamodel/models';
 import * as authActions from 'src/app/auth/auth.actions';
 import * as authSelector from 'src/app/auth/auth.selector';
 import * as datasetActions from 'src/app/metamodel/actions/dataset.actions';
 import * as datasetFamilyActions from 'src/app/metamodel/actions/dataset-family.actions';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
 import * as datasetGroupActions from 'src/app/metamodel/actions/dataset-group.actions';
+import * as webpageFamilyActions from 'src/app/metamodel/actions/webpage-family.actions';
+import * as webpageFamilySelector from 'src/app/metamodel/selectors/webpage-family.selector';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
+import * as webpageSelector from 'src/app/metamodel/selectors/webpage.selector';
 import { AppConfigService } from 'src/app/app-config.service';
 
 /**
@@ -39,22 +43,38 @@ export class InstanceComponent implements OnInit, OnDestroy {
     public favIcon: HTMLLinkElement = document.querySelector('#favicon');
     public title: HTMLLinkElement = document.querySelector('#title');
     public body: HTMLBodyElement = document.querySelector('body');
-    public links = [
-        { label: 'Home', icon: 'fas fa-home', routerLink: 'home' }
-    ];
     public instance: Observable<Instance>;
     public isAuthenticated: Observable<boolean>;
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
-    public url: Observable<string>;
+    public webpageFamilyListIsLoading: Observable<boolean>;
+    public webpageFamilyListIsLoaded: Observable<boolean>;
+    public webpageFamilyList: Observable<WebpageFamily[]>;
+    public webpageListIsLoading: Observable<boolean>;
+    public webpageListIsLoaded: Observable<boolean>;
+    public webpageList: Observable<Webpage[]>;
+    public firstWebpage: Observable<Webpage>;
     public instanceSubscription: Subscription;
+    public firstWebpageSubscription: Subscription;
 
-    constructor(private store: Store<{ }>, private config: AppConfigService, private http: HttpClient) {
+    constructor(
+        private store: Store<{ }>,
+        private config: AppConfigService,
+        private http: HttpClient,
+        private route: ActivatedRoute,
+        private router: Router
+    ) {
         this.instance = store.select(instanceSelector.selectInstanceByRouteName);
         this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
         this.userProfile = store.select(authSelector.selectUserProfile);
         this.userRoles = store.select(authSelector.selectUserRoles);
-        this.url = store.select(fromRouter.getSelectors().selectUrl);
+        this.webpageFamilyListIsLoading = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoading);
+        this.webpageFamilyListIsLoaded = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoaded);
+        this.webpageFamilyList = store.select(webpageFamilySelector.selectAllWebpageFamilies);
+        this.webpageListIsLoading = store.select(webpageSelector.selectWebpageListIsLoading);
+        this.webpageListIsLoaded = store.select(webpageSelector.selectWebpageListIsLoaded);
+        this.webpageList = store.select(webpageSelector.selectAllWebpages);
+        this.firstWebpage = store.select(webpageSelector.selectFirstWebpage);
     }
 
     ngOnInit() {
@@ -63,24 +83,22 @@ export class InstanceComponent implements OnInit, OnDestroy {
         Promise.resolve(null).then(() => this.store.dispatch(datasetFamilyActions.loadDatasetFamilyList()));
         Promise.resolve(null).then(() => this.store.dispatch(datasetActions.loadDatasetList()));
         Promise.resolve(null).then(() => this.store.dispatch(datasetGroupActions.loadDatasetGroupList()));
+        Promise.resolve(null).then(() => this.store.dispatch(webpageFamilyActions.loadWebpageFamilyList()));
+        Promise.resolve(null).then(() => this.store.dispatch(webpageActions.loadWebpageList()));
         this.instanceSubscription = this.instance.subscribe(instance => {
             if (instance) {
-                if (instance.search_by_criteria_allowed) {
-                    this.links.push({ label: instance.search_by_criteria_label, icon: 'fas fa-search', routerLink: 'search' });
-                }
-                if (instance.search_multiple_allowed) {
-                    this.links.push({ label: instance.search_multiple_label, icon: 'fas fa-search-plus', routerLink: 'search-multiple' });
-                }
-                if (instance.documentation_allowed) {
-                    this.links.push({ label: instance.documentation_label, icon: 'fas fa-question', routerLink: 'documentation' });
-                }
                 if (instance.design_favicon !== '') {
                     this.setFaviconHref(instance);
                 }
                 this.title.innerHTML = instance.label;
                 this.body.style.backgroundColor = instance.design_background_color;
             }
-        })
+        });
+        this.firstWebpageSubscription = this.firstWebpage.subscribe(webpage => {
+            if (webpage && this.router.url === '/instance/default') {
+                this.router.navigate(['webpage', webpage.id], { relativeTo: this.route });
+            }
+        });
     }
 
     setFaviconHref(instance: Instance) {
@@ -99,15 +117,6 @@ export class InstanceComponent implements OnInit, OnDestroy {
         }
     }
 
-    /**
-     * Returns application base href.
-     *
-     * @return string
-     */
-    getBaseHref(): string {
-        return this.config.baseHref;
-    }
-
     /**
      * Checks if authentication is enabled.
      *
@@ -161,5 +170,6 @@ export class InstanceComponent implements OnInit, OnDestroy {
      */
     ngOnDestroy() {
         if (this.instanceSubscription) this.instanceSubscription.unsubscribe();
+        if (this.firstWebpageSubscription) this.firstWebpageSubscription.unsubscribe();
     }
 }
diff --git a/client/src/app/instance/instance.module.ts b/client/src/app/instance/instance.module.ts
index 33a6514300c63f72977df896246e0cf0bc1135a9..a0f28693cc50fe455be948c517f5dae9883f114f 100644
--- a/client/src/app/instance/instance.module.ts
+++ b/client/src/app/instance/instance.module.ts
@@ -14,6 +14,7 @@ import { EffectsModule } from '@ngrx/effects';
 
 import { SharedModule } from 'src/app/shared/shared.module';
 import { InstanceRoutingModule, routedComponents } from './instance-routing.module';
+import { dummiesComponents } from './components';
 import { instanceReducer } from './instance.reducer';
 import { instanceEffects } from './store/effects';
 import { instanceServices } from './store/services';
@@ -30,7 +31,8 @@ import { instanceServices } from './store/services';
         EffectsModule.forFeature(instanceEffects)
     ],
     declarations: [
-        routedComponents
+        routedComponents,
+        dummiesComponents
     ],
     providers: [
         instanceServices
diff --git a/client/src/app/instance/search/components/progress-bar.component.spec.ts b/client/src/app/instance/search/components/progress-bar.component.spec.ts
index 280fd988a55b71f951dba4e327872e11ce5312cb..bc4b4f02f5e650c833dffcf7a8b008069068551f 100644
--- a/client/src/app/instance/search/components/progress-bar.component.spec.ts
+++ b/client/src/app/instance/search/components/progress-bar.component.spec.ts
@@ -54,11 +54,6 @@ describe('[Instance][Search][Component] ProgressBarComponent', () => {
             design_background_color: 'darker green',
             design_logo: 'path/to/logo',
             design_favicon: 'path/to/favicon',
-            home_component: 'HomeComponent',
-            home_component_config: {
-                home_component_text: 'Description',
-                home_component_logo: 'path/to/logo'
-            },
             samp_enabled: true,
             back_to_portal: true,
             search_by_criteria_allowed: true,
@@ -93,11 +88,6 @@ describe('[Instance][Search][Component] ProgressBarComponent', () => {
             design_background_color: 'darker green',
             design_logo: 'path/to/logo',
             design_favicon: 'path/to/favicon',
-            home_component: 'HomeComponent',
-            home_component_config: {
-                home_component_text: 'Description',
-                home_component_logo: 'path/to/logo'
-            },
             samp_enabled: true,
             back_to_portal: true,
             search_by_criteria_allowed: true,
diff --git a/client/src/app/instance/webpage/components/index.ts b/client/src/app/instance/webpage/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28c122e3ead6c199771514aeb447141d7880c7a0
--- /dev/null
+++ b/client/src/app/instance/webpage/components/index.ts
@@ -0,0 +1,5 @@
+import { WebpageComponent } from './webpage-content.component';
+
+export const dummiesComponents = [
+    WebpageComponent
+];
diff --git a/client/src/app/instance/webpage/components/webpage-content.component.html b/client/src/app/instance/webpage/components/webpage-content.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..99c5e9a4f9b14ba3431047af7a8c746abf86c8b0
--- /dev/null
+++ b/client/src/app/instance/webpage/components/webpage-content.component.html
@@ -0,0 +1 @@
+<ngx-dynamic-hooks [content]="webpage.content"></ngx-dynamic-hooks>
\ No newline at end of file
diff --git a/client/src/app/instance/webpage/components/webpage-content.component.ts b/client/src/app/instance/webpage/components/webpage-content.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8bfd9bbc62020da36e27f1b93e00eb397f52045
--- /dev/null
+++ b/client/src/app/instance/webpage/components/webpage-content.component.ts
@@ -0,0 +1,24 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input } from '@angular/core';
+
+import { Webpage } from 'src/app/metamodel/models';
+
+/**
+ * @class
+ * @classdesc Webpage content component.
+ */
+@Component({
+    selector: 'app-webpage-content',
+    templateUrl: 'webpage-content.component.html'
+})
+export class WebpageComponent {
+    @Input() webpage: Webpage;
+}
diff --git a/client/src/app/instance/webpage/containers/webpage.component.html b/client/src/app/instance/webpage/containers/webpage.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e6c9db3b20bcefbf455c1f2194083840f3bcb3c1
--- /dev/null
+++ b/client/src/app/instance/webpage/containers/webpage.component.html
@@ -0,0 +1,6 @@
+<div class="container">
+    <app-spinner *ngIf="webpageListIsLoading | async"></app-spinner>
+
+    <app-webpage-content *ngIf="webpageListIsLoaded | async" [webpage]="webpage | async">
+    </app-webpage-content>
+</div>
diff --git a/client/src/app/instance/webpage/containers/webpage.component.ts b/client/src/app/instance/webpage/containers/webpage.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b1d9d4b11572b83a4f6dd85d9420f13a67f8590e
--- /dev/null
+++ b/client/src/app/instance/webpage/containers/webpage.component.ts
@@ -0,0 +1,37 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component } from '@angular/core';
+
+import { Observable } from 'rxjs';
+import { Store } from '@ngrx/store';
+
+import { Webpage } from 'src/app/metamodel/models';
+import * as webpageSelector from 'src/app/metamodel/selectors/webpage.selector';
+
+/**
+ * @class
+ * @classdesc Webpage component.
+ */
+@Component({
+    selector: 'app-webpage',
+    templateUrl: 'webpage.component.html'
+})
+export class WebpageComponent {
+    public title: HTMLLinkElement = document.querySelector('#title');
+    public webpageListIsLoading: Observable<boolean>;
+    public webpageListIsLoaded: Observable<boolean>;
+    public webpage: Observable<Webpage>;
+
+    constructor(private store: Store<{ }>) {
+        this.webpageListIsLoading = store.select(webpageSelector.selectWebpageListIsLoading);
+        this.webpageListIsLoaded = store.select(webpageSelector.selectWebpageListIsLoaded);
+        this.webpage = this.store.select(webpageSelector.selectWebpageByRouteId);
+    }
+}
diff --git a/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.html b/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..2f43ac7ec15f1e5f671dbca64512d719766158a9
--- /dev/null
+++ b/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.html
@@ -0,0 +1,18 @@
+<a *ngIf="isExternalLink()"
+    [href]="link"
+    [ngClass]="css"
+    [target]="target ? target : '_self'"
+>
+    <ng-container *ngTemplateOutlet="contentTpl"></ng-container>
+</a>
+
+<a *ngIf="!isExternalLink()" 
+    [routerLink]="link"
+    [queryParams]="queryParams ? queryParams : {}" 
+    [fragment]="anchorFragment ? anchorFragment : null"
+    [ngClass]="css"
+>
+    <ng-container *ngTemplateOutlet="contentTpl"></ng-container>
+</a>
+
+<ng-template #contentTpl><ng-content></ng-content></ng-template>
diff --git a/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.scss b/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1f0440ee0e66b2f7ee73998e14e352af81a61627
--- /dev/null
+++ b/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.scss
@@ -0,0 +1,3 @@
+:host {
+    display: inline;
+}
\ No newline at end of file
diff --git a/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.ts b/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0e20e09d5bd91c394084aba899ed450e398f04f
--- /dev/null
+++ b/client/src/app/instance/webpage/hooks/components/dynamic-router-link.component.ts
@@ -0,0 +1,18 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+    selector: 'app-dynamic-router-link',
+    templateUrl: 'dynamic-router-link.component.html',
+    styleUrls: [ 'dynamic-router-link.component.scss' ]
+})
+export class DynamicRouterLinkComponent {
+    @Input() link: string;
+    @Input() queryParams: {[key: string]: any};
+    @Input() anchorFragment: string;
+    @Input() css: string;
+    @Input() target: string;
+
+    isExternalLink() {
+        return this.link.startsWith('http');
+    }
+}
diff --git a/client/src/app/instance/webpage/hooks/components/index.ts b/client/src/app/instance/webpage/hooks/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eb7f2756fb32360196bbf440fc5d4e80fc0475be
--- /dev/null
+++ b/client/src/app/instance/webpage/hooks/components/index.ts
@@ -0,0 +1,5 @@
+import { DynamicRouterLinkComponent } from './dynamic-router-link.component';
+
+export const hooksComponents = [
+    DynamicRouterLinkComponent
+];
diff --git a/client/src/app/instance/webpage/hooks/parsers/dynamic-router-link-parser.ts b/client/src/app/instance/webpage/hooks/parsers/dynamic-router-link-parser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cc0242d8bc7e37e7c4abfb9fe4029e5c489db115
--- /dev/null
+++ b/client/src/app/instance/webpage/hooks/parsers/dynamic-router-link-parser.ts
@@ -0,0 +1,96 @@
+import { Injectable } from '@angular/core';
+import { HookParser, HookPosition, HookValue, HookComponentData, HookBindings, HookFinder } from 'ngx-dynamic-hooks';
+
+import { DynamicRouterLinkComponent } from '../components/dynamic-router-link.component';
+
+@Injectable()
+export class DynamicRouterLinkParser implements HookParser {
+    linkOpeningTagRegex: RegExp;
+    linkClosingTagRegex: RegExp;
+    hrefAttrRegex: RegExp;
+    classAttrRegex: RegExp;
+    targetAttrRegex: RegExp;
+
+    constructor(private hookFinder: HookFinder) {
+        const hrefAttr = '\\s+href\=\\"([^\\"]*?)\\"';
+        const anyOtherAttr = '\\s+[a-zA-Z]+\\=\\"[^\\"]*?\\"';
+        const linkOpeningTag = '\\<a(?:' + anyOtherAttr + ')*?' + hrefAttr + '(?:' + anyOtherAttr + ')*?\\>';
+
+        // Transform into proper regex objects and save for later
+        this.linkOpeningTagRegex = new RegExp(linkOpeningTag, 'gim');
+        this.linkClosingTagRegex = new RegExp('<\\/a>',  'gim');
+        this.hrefAttrRegex = new RegExp(hrefAttr, 'im');
+        this.classAttrRegex = new RegExp('\\s+class\=\\"([^\\"]*?)\\"', 'im');
+        this.targetAttrRegex = new RegExp('\\s+target\=\\"([^\\"]*?)\\"', 'im')
+    }
+
+    public findHooks(content: string, context: any): Array<HookPosition> {
+        // With the regexes we prepared, we can simply use findEnclosingHooks() to retrieve
+        // the HookPositions of all internal <a>-elements from the content string
+        return this.hookFinder.findEnclosingHooks(content, this.linkOpeningTagRegex, this.linkClosingTagRegex);
+    }
+
+    public loadComponent(hookId: number, hookValue: HookValue, context: any, childNodes: Array<Element>): HookComponentData {
+        // Simply return the component class here
+        return {
+            component: DynamicRouterLinkComponent
+        };
+    }
+
+    public getBindings(hookId: number, hookValue: HookValue, context: any): HookBindings {
+        // We can reuse the hrefAttrRegex here as its first capture group is the relative part of the url, 
+        // e.g. '/jedi/windu' from 'https://www.mysite.com/jedi/windu', which is what we need
+        const hrefAttrMatch = hookValue.openingTag.match(this.hrefAttrRegex);
+        let link = hrefAttrMatch[1];
+
+        // The relative part of the link may still contain the query string and the 
+        // anchor fragment, so we need to split it up accordingly
+        const anchorFragmentSplit = link.split('#');
+        link = anchorFragmentSplit[0];
+        const anchorFragment = anchorFragmentSplit.length > 1 ? anchorFragmentSplit[1] : null;
+
+        const queryParamsSplit = link.split('?');
+        link = queryParamsSplit[0];
+        const queryParams = queryParamsSplit.length > 1 ? this.parseQueryString(queryParamsSplit[1]) : {};
+
+        // Select css part
+        let css = null;
+        const classAttrMatch = hookValue.openingTag.match(this.classAttrRegex);
+        if (classAttrMatch) {
+            css = classAttrMatch[1];
+        }
+
+        // Select target part
+        let target = null;
+        const targetAttrMatch = hookValue.openingTag.match(this.targetAttrRegex);
+        if (targetAttrMatch) {
+            target = targetAttrMatch[1];
+        }
+
+        // Give all of these to our DynamicRouterLinkComponent as inputs and we're done!
+        return {
+            inputs: {
+                link,
+                queryParams: queryParams,
+                anchorFragment: anchorFragment,
+                css,
+                target
+            }
+        };
+    }
+
+    /**
+     * A helper function that transforms a query string into a QueryParams object
+     * Approach by Wolfgang Kuehn @ https://stackoverflow.com/a/8649003/3099523
+     *
+     * @param queryParamString - The queryString to parse
+     */
+    private parseQueryString(queryParamString: string): {[key: string]: any} {
+        return JSON.parse('{"' + 
+            decodeURI(queryParamString)
+            .replace(/"/g, '\\"')
+            .replace(/&/g, '","')
+            .replace(/=/g, '":"') + 
+        '"}');
+    }
+}
diff --git a/client/src/app/instance/webpage/hooks/parsers/index.ts b/client/src/app/instance/webpage/hooks/parsers/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3dee9d17e82b7d85ef1a3566977e5212df747c17
--- /dev/null
+++ b/client/src/app/instance/webpage/hooks/parsers/index.ts
@@ -0,0 +1,7 @@
+import { HookParserEntry } from 'ngx-dynamic-hooks';
+
+import { DynamicRouterLinkParser } from './dynamic-router-link-parser';
+
+export const componentParsers: Array<HookParserEntry> = [
+    DynamicRouterLinkParser
+];
diff --git a/client/src/app/instance/home/home-routing.module.ts b/client/src/app/instance/webpage/webpage-routing.module.ts
similarity index 70%
rename from client/src/app/instance/home/home-routing.module.ts
rename to client/src/app/instance/webpage/webpage-routing.module.ts
index 539509e87ab7eebe64a7a34e7395cb62b198c17e..8350c2e16be056af67fef13f0b4d4defcfa30ce4 100644
--- a/client/src/app/instance/home/home-routing.module.ts
+++ b/client/src/app/instance/webpage/webpage-routing.module.ts
@@ -10,22 +10,22 @@
 import { NgModule } from '@angular/core';
 import { Routes, RouterModule } from '@angular/router';
 
-import { HomeComponent } from './home.component';
+import { WebpageComponent } from './containers/webpage.component';
 
 const routes: Routes = [
-    { path: '', component: HomeComponent }
+    { path: ':id', component: WebpageComponent }
 ];
 
 /**
  * @class
- * @classdesc Home routing module.
+ * @classdesc Webpage routing module.
  */
 @NgModule({
     imports: [RouterModule.forChild(routes)],
     exports: [RouterModule]
 })
-export class HomeRoutingModule { }
+export class WebpageRoutingModule { }
 
 export const routedComponents = [
-    HomeComponent
+    WebpageComponent
 ];
diff --git a/client/src/app/instance/webpage/webpage.module.ts b/client/src/app/instance/webpage/webpage.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8a57c1e92b00f6dd4062a86fd7e422521c3abeea
--- /dev/null
+++ b/client/src/app/instance/webpage/webpage.module.ts
@@ -0,0 +1,44 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { NgModule } from '@angular/core';
+
+import { DynamicHooksModule } from 'ngx-dynamic-hooks';
+
+import { SharedModule } from 'src/app/shared/shared.module';
+import { WebpageRoutingModule, routedComponents } from './webpage-routing.module';
+import { dummiesComponents } from './components';
+import { componentParsers } from './hooks/parsers';
+import { hooksComponents } from './hooks/components';
+
+/**
+ * @class
+ * @classdesc Webpage module.
+ */
+@NgModule({
+    imports: [
+        SharedModule,
+        WebpageRoutingModule,
+        DynamicHooksModule.forRoot({
+            globalParsers: componentParsers
+        }),
+    ],
+    declarations: [
+        routedComponents,
+        dummiesComponents,
+        hooksComponents
+    ],
+    providers: [
+        componentParsers
+    ],
+    entryComponents: [
+        hooksComponents
+    ]
+})
+export class WebpageModule { }
diff --git a/client/src/app/metamodel/actions/webpage-family.actions.ts b/client/src/app/metamodel/actions/webpage-family.actions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..796e33c5c0b9547567c8f3dcddc93bc866e6308b
--- /dev/null
+++ b/client/src/app/metamodel/actions/webpage-family.actions.ts
@@ -0,0 +1,25 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createAction, props } from '@ngrx/store';
+ 
+import { WebpageFamily } from '../models';
+
+export const loadWebpageFamilyList = createAction('[Metamodel] Load Webpage Family List');
+export const loadWebpageFamilyListSuccess = createAction('[Metamodel] Load Webpage Family List Success', props<{ webpageFamilies: WebpageFamily[] }>());
+export const loadWebpageFamilyListFail = createAction('[Metamodel] Load Webpage Family List Fail');
+export const addWebpageFamily = createAction('[Metamodel] Add Webpage Family', props<{ webpageFamily: WebpageFamily }>());
+export const addWebpageFamilySuccess = createAction('[Metamodel] Add Webpage Family Success', props<{ webpageFamily: WebpageFamily }>());
+export const addWebpageFamilyFail = createAction('[Metamodel] Add Webpage Family Fail');
+export const editWebpageFamily = createAction('[Metamodel] Edit Webpage Family', props<{ webpageFamily: WebpageFamily }>());
+export const editWebpageFamilySuccess = createAction('[Metamodel] Edit Webpage Family Success', props<{ webpageFamily: WebpageFamily }>());
+export const editWebpageFamilyFail = createAction('[Metamodel] Edit Webpage Family Fail');
+export const deleteWebpageFamily = createAction('[Metamodel] Delete Webpage Family', props<{ webpageFamily: WebpageFamily }>());
+export const deleteWebpageFamilySuccess = createAction('[Metamodel] Delete Webpage Family Success', props<{ webpageFamily: WebpageFamily }>());
+export const deleteWebpageFamilyFail = createAction('[Metamodel] Delete Webpage Family Fail');
diff --git a/client/src/app/metamodel/actions/webpage.actions.ts b/client/src/app/metamodel/actions/webpage.actions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e068d01b1e625fb99ae8838d564d75aeabed95e
--- /dev/null
+++ b/client/src/app/metamodel/actions/webpage.actions.ts
@@ -0,0 +1,25 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createAction, props } from '@ngrx/store';
+ 
+import { Webpage } from '../models';
+
+export const loadWebpageList = createAction('[Metamodel] Load Webpage List');
+export const loadWebpageListSuccess = createAction('[Metamodel] Load Webpage List Success', props<{ webpages: Webpage[] }>());
+export const loadWebpageListFail = createAction('[Metamodel] Load Webpage List Fail');
+export const addWebpage = createAction('[Metamodel] Add Webpage', props<{ webpage: Webpage }>());
+export const addWebpageSuccess = createAction('[Metamodel] Add Webpage Success', props<{ webpage: Webpage }>());
+export const addWebpageFail = createAction('[Metamodel] Add Webpage Fail');
+export const editWebpage = createAction('[Metamodel] Edit Webpage', props<{ webpage: Webpage }>());
+export const editWebpageSuccess = createAction('[Metamodel] Edit Webpage Success', props<{ webpage: Webpage }>());
+export const editWebpageFail = createAction('[Metamodel] Edit Webpage Fail');
+export const deleteWebpage = createAction('[Metamodel] Delete Webpage', props<{ webpage: Webpage }>());
+export const deleteWebpageSuccess = createAction('[Metamodel] Delete Webpage Success', props<{ webpage: Webpage }>());
+export const deleteWebpageFail = createAction('[Metamodel] Delete Webpage Fail');
diff --git a/client/src/app/metamodel/effects/index.ts b/client/src/app/metamodel/effects/index.ts
index 111bd9f732e401b4dc337afedbc0a66adfb68728..9f2e2fc1b4e1f176e4ee57b683a368506214a3ac 100644
--- a/client/src/app/metamodel/effects/index.ts
+++ b/client/src/app/metamodel/effects/index.ts
@@ -20,6 +20,8 @@ import { OutputFamilyEffects } from './output-family.effects';
 import { ImageEffects } from './image.effects';
 import { FileEffects } from './file.effects';
 import { ConeSearchConfigEffects } from './cone-search-config.effects'
+import { WebpageFamilyEffects } from './webpage-family.effects';
+import { WebpageEffects } from './webpage.effects';
 
 export const metamodelEffects = [
     DatabaseEffects,
@@ -34,5 +36,7 @@ export const metamodelEffects = [
     OutputFamilyEffects,
     ImageEffects,
     FileEffects,
-    ConeSearchConfigEffects
+    ConeSearchConfigEffects,
+    WebpageFamilyEffects,
+    WebpageEffects
 ];
diff --git a/client/src/app/metamodel/effects/webpage-family.effects.ts b/client/src/app/metamodel/effects/webpage-family.effects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..75732948b2e296c11fdfbe03fb52f6e507ebf86b
--- /dev/null
+++ b/client/src/app/metamodel/effects/webpage-family.effects.ts
@@ -0,0 +1,162 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+
+import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
+import { Store } from '@ngrx/store';
+import { of } from 'rxjs';
+import { map, tap, mergeMap, catchError } from 'rxjs/operators';
+import { ToastrService } from 'ngx-toastr';
+
+import * as webpageFamilyActions from '../actions/webpage-family.actions';
+import { WebpageFamilyService } from '../services/webpage-family.service';
+import * as instanceSelector from '../selectors/instance.selector';
+
+/**
+ * @class
+ * @classdesc Webpage family effects.
+ */
+@Injectable()
+export class WebpageFamilyEffects {
+    /**
+     * Calls action to retrieve webpage family list.
+     */
+    loadWebpageFamilies$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.loadWebpageFamilyList),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            mergeMap(([, instanceName]) => this.webpageFamilyService.retrieveWebpageFamilyList(instanceName)
+                .pipe(
+                    map(webpageFamilies => webpageFamilyActions.loadWebpageFamilyListSuccess({ webpageFamilies })),
+                    catchError(() => of(webpageFamilyActions.loadWebpageFamilyListFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Calls action to add a webpage family.
+     */
+    addWebpageFamily$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.addWebpageFamily),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            mergeMap(([action, instanceName]) => this.webpageFamilyService.addWebpageFamily(instanceName, action.webpageFamily)
+                .pipe(
+                    map(webpageFamily => webpageFamilyActions.addWebpageFamilySuccess({ webpageFamily })),
+                    catchError(() => of(webpageFamilyActions.addWebpageFamilyFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays add webpage family success notification.
+     */
+    addWebpageFamilySuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.addWebpageFamilySuccess),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            tap(([, instanceName]) => {
+                this.toastr.success('Webpage family successfully added', 'The new webpage family was added into the database')
+            })
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays add webpage family fail notification.
+     */
+    addWebpageFamilyFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.addWebpageFamilyFail),
+            tap(() => this.toastr.error('Failure to add webpage family', 'The new webpage family could not be added into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to modify a webpage family.
+     */
+    editWebpageFamily$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.editWebpageFamily),
+            mergeMap(action => this.webpageFamilyService.editWebpageFamily(action.webpageFamily)
+                .pipe(
+                    map(webpageFamily => webpageFamilyActions.editWebpageFamilySuccess({ webpageFamily })),
+                    catchError(() => of(webpageFamilyActions.editWebpageFamilyFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays edit webpage family success notification.
+     */
+    editWebpageFamilySuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.editWebpageFamilySuccess),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            tap(([action, instanceName]) => {
+                this.toastr.success('Webpage family successfully edited', 'The existing webpage family has been edited into the database')
+            })
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays edit webpage family error notification.
+     */
+    editWebpageFamilyFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.editWebpageFamilyFail),
+            tap(() => this.toastr.error('Failure to edit webpage family', 'The existing webpage family could not be edited into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to remove a webpage family.
+     */
+    deleteWebpageFamily$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.deleteWebpageFamily),
+            mergeMap(action => this.webpageFamilyService.deleteWebpageFamily(action.webpageFamily.id)
+                .pipe(
+                    map(() => webpageFamilyActions.deleteWebpageFamilySuccess({ webpageFamily: action.webpageFamily })),
+                    catchError(() => of(webpageFamilyActions.deleteWebpageFamilyFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays delete webpage family success notification.
+     */
+    deleteWebpageFamilySuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.deleteWebpageFamilySuccess),
+            tap(() => this.toastr.success('Webpage family successfully deleted', 'The existing webpage family has been deleted'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays delete webpage family error notification.
+     */
+    deleteWebpageFamilyFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageFamilyActions.deleteWebpageFamilyFail),
+            tap(() => this.toastr.error('Failure to delete webpage family', 'The existing webpage family could not be deleted from the database'))
+        ), { dispatch: false }
+    );
+
+    constructor(
+        private actions$: Actions,
+        private webpageFamilyService: WebpageFamilyService,
+        private toastr: ToastrService,
+        private store: Store<{ }>
+    ) {}
+}
diff --git a/client/src/app/metamodel/effects/webpage.effects.ts b/client/src/app/metamodel/effects/webpage.effects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d45f9fbb29e9e3c3dd6d878aaa34f96e12054589
--- /dev/null
+++ b/client/src/app/metamodel/effects/webpage.effects.ts
@@ -0,0 +1,165 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
+import { Store } from '@ngrx/store';
+import { of } from 'rxjs';
+import { map, tap, mergeMap, catchError } from 'rxjs/operators';
+import { ToastrService } from 'ngx-toastr';
+
+import * as webpageActions from '../actions/webpage.actions';
+import { WebpageService } from '../services/webpage.service';
+import * as instanceSelector from '../selectors/instance.selector';
+
+/**
+ * @class
+ * @classdesc Webpage effects.
+ */
+@Injectable()
+export class WebpageEffects {
+    /**
+     * Calls action to retrieve webpage list.
+     */
+    loadWebpages$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.loadWebpageList),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            mergeMap(([, instanceName]) => this.webpageService.retrieveWebpageList(instanceName)
+                .pipe(
+                    map(webpages => webpageActions.loadWebpageListSuccess({ webpages })),
+                    catchError(() => of(webpageActions.loadWebpageListFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Calls action to add a webpage.
+     */
+    addWebpage$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.addWebpage),
+            mergeMap(action => this.webpageService.addWebpage(action.webpage)
+                .pipe(
+                    map(webpage => webpageActions.addWebpageSuccess({ webpage })),
+                    catchError(() => of(webpageActions.addWebpageFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays add webpage success notification.
+     */
+    addWebpageSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.addWebpageSuccess),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            tap(([, instanceName]) => {
+                this.router.navigateByUrl(`/admin/instance/configure-instance/${instanceName}/webpage`);
+                this.toastr.success('Webpage successfully added', 'The new webpage was added into the database');
+            })
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays add webpage fail notification.
+     */
+    addWebpageFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.addWebpageFail),
+            tap(() => this.toastr.error('Failure to add webpage', 'The new webpage could not be added into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to modify a webpage.
+     */
+    editWebpage$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.editWebpage),
+            mergeMap(action => this.webpageService.editWebpage(action.webpage)
+                .pipe(
+                    map(webpage => webpageActions.editWebpageSuccess({ webpage })),
+                    catchError(() => of(webpageActions.editWebpageFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays edit webpage success notification.
+     */
+    editWebpageSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.editWebpageSuccess),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            tap(([, instanceName]) => {
+                this.router.navigateByUrl(`/admin/instance/configure-instance/${instanceName}/webpage`);
+                this.toastr.success('Webpage successfully edited', 'The existing webpage has been edited into the database')
+            })
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays edit webpage error notification.
+     */
+    editWebpageFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.editWebpageFail),
+            tap(() => this.toastr.error('Failure to edit webpage', 'The existing webpage could not be edited into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to remove a webpage.
+     */
+    deleteWebpage$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.deleteWebpage),
+            mergeMap(action => this.webpageService.deleteWebpage(action.webpage.id)
+                .pipe(
+                    map(() => webpageActions.deleteWebpageSuccess({ webpage: action.webpage })),
+                    catchError(() => of(webpageActions.deleteWebpageFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays delete webpage success notification.
+     */
+    deleteWebpageSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.deleteWebpageSuccess),
+            tap(() => this.toastr.success('Webpage successfully deleted', 'The existing webpage has been deleted'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays delete webpage error notification.
+     */
+    deleteWebpageFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.deleteWebpageFail),
+            tap(() => this.toastr.error('Failure to delete webpage', 'The existing webpage could not be deleted from the database'))
+        ), { dispatch: false }
+    );
+
+    constructor(
+        private actions$: Actions,
+        private webpageService: WebpageService,
+        private router: Router,
+        private toastr: ToastrService,
+        private store: Store<{ }>
+    ) {}
+}
diff --git a/client/src/app/metamodel/metamodel.reducer.ts b/client/src/app/metamodel/metamodel.reducer.ts
index 2cccdfc3787cfaac8e61e759cd38e77e93f7daa0..017bb208bf52fad13a7d7d9ffef312fd6bdd2e3b 100644
--- a/client/src/app/metamodel/metamodel.reducer.ts
+++ b/client/src/app/metamodel/metamodel.reducer.ts
@@ -23,6 +23,8 @@ import * as outputFamily from './reducers/output-family.reducer';
 import * as image from './reducers/image.reducer';
 import * as file from './reducers/file.reducer';
 import * as coneSearchConfig from './reducers/cone-search-config.reducer';
+import * as webpageFamily from './reducers/webpage-family.reducer';
+import * as webpage from './reducers/webpage.reducer';
 
 /**
  * Interface for metamodel state.
@@ -43,6 +45,8 @@ export interface State {
     image: image.State;
     file: file.State;
     coneSearchConfig: coneSearchConfig.State;
+    webpageFamily: webpageFamily.State;
+    webpage: webpage.State;
 }
 
 const reducers = {
@@ -58,7 +62,9 @@ const reducers = {
     outputFamily: outputFamily.outputFamilyReducer,
     image: image.imageReducer,
     file: file.fileReducer,
-    coneSearchConfig: coneSearchConfig.coneSearchConfigReducer
+    coneSearchConfig: coneSearchConfig.coneSearchConfigReducer,
+    webpageFamily: webpageFamily.webpageFamilyReducer,
+    webpage: webpage.webpageReducer
 };
 
 export const metamodelReducer = combineReducers(reducers);
diff --git a/client/src/app/metamodel/models/index.ts b/client/src/app/metamodel/models/index.ts
index 82f01695472cea1a22424bf1a7465fdcd6f168b5..976dea69f42d52915527e4df612fd2a076f406e0 100644
--- a/client/src/app/metamodel/models/index.ts
+++ b/client/src/app/metamodel/models/index.ts
@@ -22,4 +22,6 @@ export * from './image.model';
 export * from './renderers';
 export * from './detail-renderers';
 export * from './cone-search-config.model';
-export * from './file.model';
\ No newline at end of file
+export * from './file.model';
+export * from './webpage.model';
+export * from './webpage-family';
diff --git a/client/src/app/metamodel/models/instance.model.ts b/client/src/app/metamodel/models/instance.model.ts
index 6294572235d3f6826cf09f9fbd4b28ed04e3fd91..9a467958d036e56fa5d07560da2a155bd36546b9 100644
--- a/client/src/app/metamodel/models/instance.model.ts
+++ b/client/src/app/metamodel/models/instance.model.ts
@@ -28,11 +28,6 @@ export interface Instance {
     design_background_color: string;
     design_logo: string;
     design_favicon: string;
-    home_component: string;
-    home_component_config: {
-        home_component_text: string;
-        home_component_logo: string;
-    };
     samp_enabled: boolean;
     back_to_portal: boolean;
     search_by_criteria_allowed: boolean;
diff --git a/client/src/app/metamodel/models/webpage-family.ts b/client/src/app/metamodel/models/webpage-family.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c85583ee497cc961bd6fb59647839851a39555e
--- /dev/null
+++ b/client/src/app/metamodel/models/webpage-family.ts
@@ -0,0 +1,20 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for webpage family.
+ *
+ * @interface WebpageFamily
+ */
+ export interface WebpageFamily {
+    id: number;
+    label: string;
+    icon: string;
+    display: number;
+}
diff --git a/client/src/app/metamodel/models/webpage.model.ts b/client/src/app/metamodel/models/webpage.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7f37e6d5ad87de24f8870fd6c1f10e08db729545
--- /dev/null
+++ b/client/src/app/metamodel/models/webpage.model.ts
@@ -0,0 +1,23 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for web page.
+ *
+ * @interface Webpage
+ */
+export interface Webpage {
+    id: number;
+    label: string;
+    icon: string;
+    display: number;
+    title: string;
+    content: string;
+    id_webpage_family: number;
+}
diff --git a/client/src/app/metamodel/reducers/webpage-family.reducer.ts b/client/src/app/metamodel/reducers/webpage-family.reducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8f4ba68c5cb4a17601d3e4019c129ba2c21c848a
--- /dev/null
+++ b/client/src/app/metamodel/reducers/webpage-family.reducer.ts
@@ -0,0 +1,84 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createReducer, on } from '@ngrx/store';
+import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
+
+import { WebpageFamily } from '../models';
+import * as webpageFamilyActions from '../actions/webpage-family.actions';
+
+/**
+ * Interface for webpage family state.
+ *
+ * @interface State
+ */
+export interface State extends EntityState<WebpageFamily> {
+    webpageFamilyListIsLoading: boolean;
+    webpageFamilyListIsLoaded: boolean;
+}
+
+export const adapter: EntityAdapter<WebpageFamily> = createEntityAdapter<WebpageFamily>({
+    selectId: (webpageFamily: WebpageFamily) => webpageFamily.id,
+    sortComparer: (a: WebpageFamily, b: WebpageFamily) => a.display - b.display
+});
+
+export const initialState: State = adapter.getInitialState({
+    webpageFamilyListIsLoading: false,
+    webpageFamilyListIsLoaded: false
+});
+
+export const webpageFamilyReducer = createReducer(
+    initialState,
+    on(webpageFamilyActions.loadWebpageFamilyList, (state) => {
+        return {
+            ...state,
+            webpageFamilyListIsLoading: true
+        }
+    }),
+    on(webpageFamilyActions.loadWebpageFamilyListSuccess, (state, { webpageFamilies }) => {
+        return adapter.setAll(
+            webpageFamilies,
+            {
+                ...state,
+                webpageFamilyListIsLoading: false,
+                webpageFamilyListIsLoaded: true
+            }
+        );
+    }),
+    on(webpageFamilyActions.loadWebpageFamilyListFail, (state) => {
+        return {
+            ...state,
+            webpageFamilyListIsLoading: false
+        }
+    }),
+    on(webpageFamilyActions.addWebpageFamilySuccess, (state, { webpageFamily }) => {
+        return adapter.addOne(webpageFamily, state)
+    }),
+    on(webpageFamilyActions.editWebpageFamilySuccess, (state, { webpageFamily }) => {
+        return adapter.setOne(webpageFamily, state)
+    }),
+    on(webpageFamilyActions.deleteWebpageFamilySuccess, (state, { webpageFamily }) => {
+        return adapter.removeOne(webpageFamily.id, state)
+    })
+);
+
+const {
+    selectIds,
+    selectEntities,
+    selectAll,
+    selectTotal,
+} = adapter.getSelectors();
+
+export const selectWebpageFamilyIds = selectIds;
+export const selectWebpageFamilyEntities = selectEntities;
+export const selectAllWebpageFamilies = selectAll;
+export const selectWebpageFamilyTotal = selectTotal;
+
+export const selectWebpageFamilyListIsLoading = (state: State) => state.webpageFamilyListIsLoading;
+export const selectWebpageFamilyListIsLoaded = (state: State) => state.webpageFamilyListIsLoaded;
diff --git a/client/src/app/metamodel/reducers/webpage.reducer.ts b/client/src/app/metamodel/reducers/webpage.reducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd0a982086bef28ee5cfd7134ff482f5666cd4c2
--- /dev/null
+++ b/client/src/app/metamodel/reducers/webpage.reducer.ts
@@ -0,0 +1,84 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createReducer, on } from '@ngrx/store';
+import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
+
+import { Webpage } from '../models';
+import * as webpageActions from '../actions/webpage.actions';
+
+/**
+ * Interface for webpage state.
+ *
+ * @interface State
+ */
+export interface State extends EntityState<Webpage> {
+    webpageListIsLoading: boolean;
+    webpageListIsLoaded: boolean;
+}
+
+export const adapter: EntityAdapter<Webpage> = createEntityAdapter<Webpage>({
+    selectId: (webpage: Webpage) => webpage.id,
+    sortComparer: (a: Webpage, b: Webpage) => a.display - b.display
+});
+
+export const initialState: State = adapter.getInitialState({
+    webpageListIsLoading: false,
+    webpageListIsLoaded: false
+});
+
+export const webpageReducer = createReducer(
+    initialState,
+    on(webpageActions.loadWebpageList, (state) => {
+        return {
+            ...state,
+            webpageListIsLoading: true
+        }
+    }),
+    on(webpageActions.loadWebpageListSuccess, (state, { webpages }) => {
+        return adapter.setAll(
+            webpages,
+            {
+                ...state,
+                webpageListIsLoading: false,
+                webpageListIsLoaded: true
+            }
+        );
+    }),
+    on(webpageActions.loadWebpageListFail, (state) => {
+        return {
+            ...state,
+            webpageListIsLoading: false
+        }
+    }),
+    on(webpageActions.addWebpageSuccess, (state, { webpage }) => {
+        return adapter.addOne(webpage, state)
+    }),
+    on(webpageActions.editWebpageSuccess, (state, { webpage }) => {
+        return adapter.setOne(webpage, state)
+    }),
+    on(webpageActions.deleteWebpageSuccess, (state, { webpage }) => {
+        return adapter.removeOne(webpage.id, state)
+    })
+);
+
+const {
+    selectIds,
+    selectEntities,
+    selectAll,
+    selectTotal,
+} = adapter.getSelectors();
+
+export const selectWebpageIds = selectIds;
+export const selectWebpageEntities = selectEntities;
+export const selectAllWebpages = selectAll;
+export const selectWebpageTotal = selectTotal;
+
+export const selectWebpageListIsLoading = (state: State) => state.webpageListIsLoading;
+export const selectWebpageListIsLoaded = (state: State) => state.webpageListIsLoaded;
diff --git a/client/src/app/metamodel/selectors/webpage-family.selector.ts b/client/src/app/metamodel/selectors/webpage-family.selector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c425422511008e57824062d9cf9684232e73b54
--- /dev/null
+++ b/client/src/app/metamodel/selectors/webpage-family.selector.ts
@@ -0,0 +1,48 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createSelector } from '@ngrx/store';
+
+import * as reducer from '../metamodel.reducer';
+import * as fromWebpageFamily from '../reducers/webpage-family.reducer';
+
+export const selectWebpageFamilyState = createSelector(
+    reducer.getMetamodelState,
+    (state: reducer.State) => state.webpageFamily
+);
+
+export const selectWebpageFamilyIds = createSelector(
+    selectWebpageFamilyState,
+    fromWebpageFamily.selectWebpageFamilyIds
+);
+
+export const selectWebpageFamilyEntities = createSelector(
+    selectWebpageFamilyState,
+    fromWebpageFamily.selectWebpageFamilyEntities
+);
+
+export const selectAllWebpageFamilies = createSelector(
+    selectWebpageFamilyState,
+    fromWebpageFamily.selectAllWebpageFamilies
+);
+
+export const selectWebpageFamilyTotal = createSelector(
+    selectWebpageFamilyState,
+    fromWebpageFamily.selectWebpageFamilyTotal
+);
+
+export const selectWebpageFamilyListIsLoading = createSelector(
+    selectWebpageFamilyState,
+    fromWebpageFamily.selectWebpageFamilyListIsLoading
+);
+
+export const selectWebpageFamilyListIsLoaded = createSelector(
+    selectWebpageFamilyState,
+    fromWebpageFamily.selectWebpageFamilyListIsLoaded
+);
diff --git a/client/src/app/metamodel/selectors/webpage.selector.ts b/client/src/app/metamodel/selectors/webpage.selector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20a5be4d3a8417f5e205cf4c0f1323ffcb612899
--- /dev/null
+++ b/client/src/app/metamodel/selectors/webpage.selector.ts
@@ -0,0 +1,64 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createSelector } from '@ngrx/store';
+
+import * as reducer from '../metamodel.reducer';
+import * as fromWebpage from '../reducers/webpage.reducer';
+import * as webpageFamilySelector from './webpage-family.selector';
+
+export const selectWebpageState = createSelector(
+    reducer.getMetamodelState,
+    (state: reducer.State) => state.webpage
+);
+
+export const selectWebpageIds = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageIds
+);
+
+export const selectWebpageEntities = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageEntities
+);
+
+export const selectAllWebpages = createSelector(
+    selectWebpageState,
+    fromWebpage.selectAllWebpages
+);
+
+export const selectWebpageTotal = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageTotal
+);
+
+export const selectWebpageListIsLoading = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageListIsLoading
+);
+
+export const selectWebpageListIsLoaded = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageListIsLoaded
+);
+
+export const selectWebpageByRouteId = createSelector(
+    selectWebpageEntities,
+    reducer.selectRouterState,
+    (entities, router) => entities[router.state.params.id]
+);
+
+export const selectFirstWebpage = createSelector(
+    webpageFamilySelector.selectAllWebpageFamilies,
+    selectAllWebpages,
+    (webpageFamilyList, webpageList) => {
+        const firstWebpageFamily = webpageFamilyList[0];
+        return webpageList.filter(webpage => webpage.id_webpage_family === firstWebpageFamily.id)[0];
+    }
+);
\ No newline at end of file
diff --git a/client/src/app/metamodel/services/index.ts b/client/src/app/metamodel/services/index.ts
index c24336c01c0642c22818a75ee76c8be5670a9f0e..1c670871ca257a1f0da700f2ce2f45baa6e8fcd7 100644
--- a/client/src/app/metamodel/services/index.ts
+++ b/client/src/app/metamodel/services/index.ts
@@ -20,6 +20,8 @@ import { OutputFamilyService } from './output-family.service';
 import { ImageService } from './image.service';
 import { FileService } from './file.service';
 import { ConeSearchConfigService } from './cone-search-config.service';
+import { WebpageFamilyService } from './webpage-family.service';
+import { WebpageService } from './webpage.service';
 
 export const metamodelServices = [
     DatabaseService,
@@ -34,5 +36,7 @@ export const metamodelServices = [
     OutputFamilyService,
     ImageService,
     FileService,
-    ConeSearchConfigService
+    ConeSearchConfigService,
+    WebpageFamilyService,
+    WebpageService
 ];
diff --git a/client/src/app/metamodel/services/webpage-family.service.ts b/client/src/app/metamodel/services/webpage-family.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..510ac66167f58a0345b8d155d394ac9aa96da7d0
--- /dev/null
+++ b/client/src/app/metamodel/services/webpage-family.service.ts
@@ -0,0 +1,70 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { Observable } from 'rxjs';
+
+import { WebpageFamily } from '../models';
+import { AppConfigService } from 'src/app/app-config.service';
+
+/**
+ * @class
+ * @classdesc Webpage family service.
+ */
+@Injectable()
+export class WebpageFamilyService {
+    constructor(private http: HttpClient, private config: AppConfigService) { }
+
+    /**
+     * Retrieves webpage families for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     *
+     * @return Observable<WebpageFamily>
+     */
+    retrieveWebpageFamilyList(instanceName: string): Observable<WebpageFamily[]> {
+        return this.http.get<WebpageFamily[]>(`${this.config.apiUrl}/instance/${instanceName}/webpage-family`);
+    }
+
+    /**
+     * Adds a new webpage family for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     * @param  {WebpageFamily} newWebpageFamily - The webpage family.
+     *
+     * @return Observable<WebpageFamily>
+     */
+    addWebpageFamily(instanceName: string, newWebpageFamily: WebpageFamily): Observable<WebpageFamily> {
+        return this.http.post<WebpageFamily>(`${this.config.apiUrl}/instance/${instanceName}/webpage-family`, newWebpageFamily);
+    }
+
+    /**
+     * Modifies a webpage family.
+     *
+     * @param  {WebpageFamily} webpageFamily - The webpage family.
+     *
+     * @return Observable<WebpageFamily>
+     */
+    editWebpageFamily(webpageFamily: WebpageFamily): Observable<WebpageFamily> {
+        return this.http.put<WebpageFamily>(`${this.config.apiUrl}/webpage-family/${webpageFamily.id}`, webpageFamily);
+    }
+
+    /**
+     * Removes a webpage family.
+     *
+     * @param  {number} webpageFamilyId - The webpage family ID.
+     *
+     * @return Observable<object>
+     */
+    deleteWebpageFamily(webpageFamilyId: number): Observable<object> {
+        return this.http.delete(`${this.config.apiUrl}/webpage-family/${webpageFamilyId}`);
+    }
+}
diff --git a/client/src/app/metamodel/services/webpage.service.ts b/client/src/app/metamodel/services/webpage.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac38b3589fe361a462e4304e0727f3bd3fd5f67e
--- /dev/null
+++ b/client/src/app/metamodel/services/webpage.service.ts
@@ -0,0 +1,69 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { Observable } from 'rxjs';
+
+import { Webpage } from '../models';
+import { AppConfigService } from 'src/app/app-config.service';
+
+/**
+ * @class
+ * @classdesc Webpage service.
+ */
+@Injectable()
+export class WebpageService {
+    constructor(private http: HttpClient, private config: AppConfigService) { }
+
+    /**
+     * Retrieves webpages for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     *
+     * @return Observable<Webpage[]>
+     */
+    retrieveWebpageList(instanceName: string): Observable<Webpage[]> {
+        return this.http.get<Webpage[]>(`${this.config.apiUrl}/instance/${instanceName}/webpage`);
+    }
+
+    /**
+     * Adds a new webpage for the given instance.
+     *
+     * @param  {Webpage} newWebpage - The webpage.
+     *
+     * @return Observable<Webpage>
+     */
+    addWebpage(newWebpage: Webpage): Observable<Webpage> {
+        return this.http.post<Webpage>(`${this.config.apiUrl}/webpage-family/${newWebpage.id_webpage_family}/webpage`, newWebpage);
+    }
+
+    /**
+     * Modifies a webpage.
+     *
+     * @param  {Webpage} webpage - The Webpage.
+     *
+     * @return Observable<Webpage>
+     */
+    editWebpage(webpage: Webpage): Observable<Webpage> {
+        return this.http.put<Webpage>(`${this.config.apiUrl}/webpage/${webpage.id}`, webpage);
+    }
+
+    /**
+     * Removes a webpage.
+     *
+     * @param  {number} webpageId - The webpage ID.
+     *
+     * @return Observable<object>
+     */
+    deleteWebpage(webpageId: number): Observable<object> {
+        return this.http.delete(`${this.config.apiUrl}/webpage/${webpageId}`);
+    }
+}
diff --git a/client/src/app/portal/components/index.ts b/client/src/app/portal/components/index.ts
index 20812c31b8e9f4c4c6456920902086940296a386..685b2995ee7cafc764e12b01b5f2d5becd7bd4fd 100644
--- a/client/src/app/portal/components/index.ts
+++ b/client/src/app/portal/components/index.ts
@@ -8,7 +8,9 @@
  */
 
 import { InstanceCardComponent } from './instance-card.component';
+import { PortalNavbarComponent } from './portal-navbar.component';
 
 export const dummiesComponents = [
-    InstanceCardComponent
+    InstanceCardComponent,
+    PortalNavbarComponent
 ];
diff --git a/client/src/app/portal/components/portal-navbar.component.html b/client/src/app/portal/components/portal-navbar.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..b253077a43932bf1eeb8215b9b3f9b0f89bf436b
--- /dev/null
+++ b/client/src/app/portal/components/portal-navbar.component.html
@@ -0,0 +1,93 @@
+<nav class="navbar navbar-light bg-light navbar-expand-md fixed-top border-bottom">
+    <!-- Logo -->
+    <a routerLink="/" class="navbar-brand">
+        <img src="assets/cesam_anis40.png" alt="ANIS logo" />
+    </a>
+
+    <!-- Navigation -->
+    <div class="collapse navbar-collapse" id="navbarCollapse">
+        <ul class="navbar-nav mr-auto"></ul>
+        <ul class="navbar-nav justify-content-end">
+            <li *ngIf="!authenticationEnabled" class="nav-item pr-3">
+                <a class="nav-link" routerLink="/admin" routerLinkActive="active">
+                    <span class="fas fa-tools"></span> Admin
+                </a>
+            </li>
+        </ul>
+        <!-- sign in / sign out -->
+        <button *ngIf="authenticationEnabled && !isAuthenticated"
+            class="btn btn-outline-success my-2 my-sm-0"
+            id="button-sign-in"
+            (click)="login.emit()">
+            Sign In / Register
+        </button>
+        <span *ngIf="isAuthenticated" id="dropdown-menu" dropdown>
+            <button id="button-basic" dropdownToggle type="button" class="btn btn-light" aria-controls="dropdown-basic">
+                <span class="fa-stack" [ngStyle]="{ color: '#7AC29A' }">
+                    <span class="fas fa-circle fa-2x"></span>
+                    <span class="fas fa-user fa-stack-1x fa-inverse"></span>
+                </span>
+                &nbsp;
+                <span class="fas fa-chevron-down text-secondary"></span>
+            </button>
+            <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu" aria-labelledby="basic-link">
+                <li id="li-email" role="menuitem">
+                    <span class="dropdown-item font-italic">{{ userProfile.email }}</span>
+                </li>
+                <li class="divider dropdown-divider"></li>
+                <li role="menuitem">
+                    <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin">
+                        <span class="fas fa-tools"></span> Admin
+                    </a>
+                    <a class="dropdown-item pointer" (click)="openEditProfile.emit()">
+                        <span class="fas fa-id-card"></span> Edit profile
+                    </a>
+                </li>
+                <li class="divider dropdown-divider"></li>
+                <li role="menuitem">
+                    <a class="dropdown-item text-danger pointer" (click)="logout.emit()">
+                        <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out
+                    </a>
+                </li>
+            </ul>
+        </span>
+    </div>
+
+    <!-- Navigation Mobile -->
+    <span dropdown>
+        <button id="button-basic" dropdownToggle type="button" class="navbar-toggler" aria-controls="dropdown-basic">
+            <span class="fas fa-bars"></span>
+        </button>
+        <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu"
+            aria-labelledby="basic-link">
+            <li *ngIf="isAuthenticated" role="menuitem">
+                <span class="dropdown-item font-italic">{{ userProfile.email }}</span>
+            </li>
+            <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li>
+            <li *ngIf="!authenticationEnabled" role="menuitem">
+                <a class="dropdown-item pointer" routerLink="/admin">
+                    <span class="fas fa-tools"></span> Admin
+                </a>
+            </li>
+            <li *ngIf="isAuthenticated" role="menuitem">
+                <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin">
+                    <span class="fas fa-tools"></span> Admin
+                </a>
+                <a class="dropdown-item pointer" (click)="openEditProfile.emit()">
+                    <span class="fas fa-id-card"></span> Edit profile
+                </a>
+            </li>
+            <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li>
+            <li *ngIf="authenticationEnabled && !isAuthenticated" role="menuitem">
+                <a class="dropdown-item pointer text-success" (click)="login.emit()">
+                    <span class="fas fa-sign-in-alt fa-fw"></span> Sign In / Register
+                </a>
+            </li>
+            <li *ngIf="isAuthenticated" role="menuitem">
+                <a class="dropdown-item pointer text-danger" (click)="logout.emit()">
+                    <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out
+                </a>
+            </li>
+        </ul>
+    </span>
+</nav>
diff --git a/client/src/app/portal/components/portal-navbar.component.scss b/client/src/app/portal/components/portal-navbar.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1cc4ce8a2b777fb65944b30cbb3363caf453b52f
--- /dev/null
+++ b/client/src/app/portal/components/portal-navbar.component.scss
@@ -0,0 +1,17 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+.dropdown-up {
+    top: 80% !important;
+    right: 5px !important;
+}
+
+img {
+    height: 60px;
+}
diff --git a/client/src/app/portal/components/portal-navbar.component.ts b/client/src/app/portal/components/portal-navbar.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b8d0ab4b1719b5cf797d1c9bd2df523637883815
--- /dev/null
+++ b/client/src/app/portal/components/portal-navbar.component.ts
@@ -0,0 +1,30 @@
+import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core';
+
+import { UserProfile } from 'src/app/auth/user-profile.model';
+import { isAdmin } from 'src/app/shared/utils';
+
+@Component({
+    selector: 'app-portal-navbar',
+    templateUrl: 'portal-navbar.component.html',
+    styleUrls: [ 'portal-navbar.component.scss' ],
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class PortalNavbarComponent {
+    @Input() isAuthenticated: boolean;
+    @Input() userProfile: UserProfile = null;
+    @Input() userRoles: string[];
+    @Input() authenticationEnabled: boolean;
+    @Input() adminRoles: string[];
+    @Output() login: EventEmitter<any> = new EventEmitter();
+    @Output() logout: EventEmitter<any> = new EventEmitter();
+    @Output() openEditProfile: EventEmitter<any> = new EventEmitter();
+
+    /**
+     * Returns true if user is admin
+     * 
+     * @returns boolean
+     */
+    isAdmin() {
+        return isAdmin(this.adminRoles, this.userRoles);
+    }
+}
diff --git a/client/src/app/portal/containers/portal-home.component.html b/client/src/app/portal/containers/portal-home.component.html
index 29c5b00fe27cdae382d870f9afc4b0861280b62d..ba726a547cb4689f290c2be833c770c860bb8301 100644
--- a/client/src/app/portal/containers/portal-home.component.html
+++ b/client/src/app/portal/containers/portal-home.component.html
@@ -1,17 +1,14 @@
 <header>
-    <app-navbar
-        [links]="links"
+    <app-portal-navbar
         [isAuthenticated]="isAuthenticated | async"
         [userProfile]="userProfile | async"
         [userRoles]="userRoles | async"
-        [baseHref]="getBaseHref()"
         [authenticationEnabled]="getAuthenticationEnabled()"
         [adminRoles]="getAdminRoles()"
-        [url]="url | async"
         (login)="login()"
         (logout)="logout()"
         (openEditProfile)="openEditProfile()">
-    </app-navbar>
+    </app-portal-navbar>
 </header>
 <main role="main" class="container-fluid pb-4">
     <div class="container">
diff --git a/client/src/app/portal/containers/portal-home.component.spec.ts b/client/src/app/portal/containers/portal-home.component.spec.ts
index 66dd2b6217b1db9aba6b6c3e28eb37e6b7fb0f04..0c9d4f20e9cfe76e2e21da30d9a6ac10bb800087 100644
--- a/client/src/app/portal/containers/portal-home.component.spec.ts
+++ b/client/src/app/portal/containers/portal-home.component.spec.ts
@@ -53,11 +53,6 @@ describe('[Instance][Portal][Container] PortalHomeComponent', () => {
         expect(component).toBeDefined();
     });
 
-    it('#getBaseHref() should return base href config key value', () => {
-        appConfigServiceStub.baseHref = '/my-project';
-        expect(component.getBaseHref()).toBe('/my-project');
-    });
-
     it('#authenticationEnabled() should return authentication enabled config key value', () => {
         appConfigServiceStub.authenticationEnabled = true;
         expect(component.getAuthenticationEnabled()).toBeTruthy();
diff --git a/client/src/app/portal/containers/portal-home.component.ts b/client/src/app/portal/containers/portal-home.component.ts
index ceb30652b6bd4251dfe7971389d5f02bb1ca515c..56ea525430382c9f1b143d912dcc3951e5518334 100644
--- a/client/src/app/portal/containers/portal-home.component.ts
+++ b/client/src/app/portal/containers/portal-home.component.ts
@@ -11,7 +11,6 @@ import { Component, OnInit } from '@angular/core';
 
 import { Observable, Subscription } from 'rxjs';
 import { Store } from '@ngrx/store';
-import * as fromRouter from '@ngrx/router-store';
 
 import { UserProfile } from 'src/app/auth/user-profile.model';
 import { Instance, InstanceGroup } from 'src/app/metamodel/models';
@@ -36,13 +35,11 @@ export class PortalHomeComponent implements OnInit {
     public favIcon: HTMLLinkElement = document.querySelector('#favicon');
     public title: HTMLLinkElement = document.querySelector('#title');
     public body: HTMLBodyElement = document.querySelector('body');
-    public links = [];
     public isAuthenticated: Observable<boolean>;
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
     public instanceList: Observable<Instance[]>;
     public instanceGroupList: Observable<InstanceGroup[]>;
-    public url: Observable<string>;
     public userRolesSubscription: Subscription;
 
     constructor(private store: Store<{ }>, private config: AppConfigService) {
@@ -51,7 +48,6 @@ export class PortalHomeComponent implements OnInit {
         this.userRoles = store.select(authSelector.selectUserRoles);
         this.instanceList = store.select(instanceSelector.selectAllInstances);
         this.instanceGroupList = store.select(instanceGroupSelector.selectAllInstanceGroups);
-        this.url = store.select(fromRouter.getSelectors().selectUrl);
     }
 
     ngOnInit() {
@@ -60,15 +56,6 @@ export class PortalHomeComponent implements OnInit {
         this.body.style.backgroundColor = 'white';
     }
 
-    /**
-     * Returns application base href.
-     *
-     * @return string
-     */
-    getBaseHref(): string {
-        return this.config.baseHref;
-    }
-
     /**
      * Checks if authentication is enabled.
      *
diff --git a/client/src/app/shared/components/index.ts b/client/src/app/shared/components/index.ts
index 87259b3c83248b10b2b7058ac79b71127a11b7ca..18a3063846b295a531f6311a1637ce0c83265947 100644
--- a/client/src/app/shared/components/index.ts
+++ b/client/src/app/shared/components/index.ts
@@ -8,9 +8,7 @@
  */
 
 import { SpinnerComponent } from "./spinner.component";
-import { NavbarComponent } from './navbar.component';
 
 export const sharedComponents = [
-    SpinnerComponent,
-    NavbarComponent
+    SpinnerComponent
 ];
diff --git a/client/src/app/shared/components/navbar.component.spec.ts b/client/src/app/shared/components/navbar.component.spec.ts
deleted file mode 100644
index c7c9debb79f73e155085b3d568c820ef6951cdc9..0000000000000000000000000000000000000000
--- a/client/src/app/shared/components/navbar.component.spec.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { TestBed, ComponentFixture  } from '@angular/core/testing';
-import { RouterTestingModule } from '@angular/router/testing';
-
-import { NavbarComponent } from './navbar.component';
-import { INSTANCE } from '../../../test-data';
-
-describe('[Shared][Component] NavbarComponent', () => {
-    let component: NavbarComponent;
-    let fixture: ComponentFixture<NavbarComponent>;
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [NavbarComponent],
-            imports: [RouterTestingModule]
-
-        }).compileComponents();
-        fixture = TestBed.createComponent(NavbarComponent);
-        component = fixture.componentInstance;
-    });
-
-    it('should create the component', () => {
-        expect(component).toBeDefined();
-    });
-
-    it('should return logo href', () => {
-        component.apiUrl = 'http://test.com';
-        component.instance = INSTANCE;
-        expect(component.getLogoHref()).toEqual('http://test.com/instance/myInstance/file-explorer/path/to/logo');
-        component.instance.design_logo = '';
-        expect(component.getLogoHref()).toEqual('assets/cesam_anis40.png');
-    });
-});
diff --git a/client/src/app/shared/pipes/index.ts b/client/src/app/shared/pipes/index.ts
index 6f5205846b9ae9917c7d1b1ec71919e4d4b5290d..4c74ce3d070a9d0bc96dd779715b3a4af8315ef5 100644
--- a/client/src/app/shared/pipes/index.ts
+++ b/client/src/app/shared/pipes/index.ts
@@ -13,6 +13,7 @@ import { OutputFamilyByIdPipe } from './output-family-by-id.pipe';
 import { DatasetByNamePipe } from './dataset-by-name.pipe';
 import { InstanceByNamePipe } from './instance-by-name.pipe';
 import { AuthImagePipe } from './auth-image.pipe';
+import { WebpageListByFamilyPipe } from './webpage-list-by-family.pipe';
 
 export const sharedPipes = [
     DatasetListByFamilyPipe,
@@ -20,5 +21,6 @@ export const sharedPipes = [
     OutputFamilyByIdPipe,
     DatasetByNamePipe,
     InstanceByNamePipe,
-    AuthImagePipe
+    AuthImagePipe,
+    WebpageListByFamilyPipe
 ];
diff --git a/client/src/app/shared/pipes/webpage-list-by-family.pipe.ts b/client/src/app/shared/pipes/webpage-list-by-family.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e93b931bb4169c5d0215ad4c980334212a6f9ceb
--- /dev/null
+++ b/client/src/app/shared/pipes/webpage-list-by-family.pipe.ts
@@ -0,0 +1,27 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Pipe, PipeTransform } from '@angular/core';
+
+import { Webpage } from 'src/app/metamodel/models';
+
+/**
+ * @class
+ * @classdesc Returns webpages corresponding to the given webpage family ID.
+ *
+ * @example
+ * // returns webpages that matching with the webpage family ID among the webpage list
+ * {{ webpageList | webpageListByFamily:1 }}
+ */
+@Pipe({ name: 'webpageListByFamily' })
+export class WebpageListByFamilyPipe implements PipeTransform {
+    transform(webpageList: Webpage[], idWebpageFamily: number): Webpage[] {
+        return webpageList.filter(webpage => webpage.id_webpage_family === idWebpageFamily);
+    }
+}
diff --git a/client/src/styles.scss b/client/src/styles.scss
index 2f276ed780c804c7a33845a14cafaff9d99f1680..d706a62faffc0855002d27a111992b3351d8ab67 100644
--- a/client/src/styles.scss
+++ b/client/src/styles.scss
@@ -79,11 +79,11 @@ main {
 }
 
 /* Angular forms */
-input.ng-valid, select.ng-valid, .ng-select.ng-valid div.ng-select-container, textarea.ng-valid {
+input.ng-valid, select.ng-valid, .ng-select.ng-valid div.ng-select-container, textarea.ng-valid, editor.ng-valid {
     border-left: 5px solid #42A948; /* green */
 }
   
-input.ng-invalid, select.ng-invalid, .ng-select.ng-invalid div.ng-select-container, textarea.ng-invalid {
+input.ng-invalid, select.ng-invalid, .ng-select.ng-invalid div.ng-select-container, textarea.ng-invalid, editor.ng-invalid {
     border-left: 5px solid #a94442; /* red */
 }
 
diff --git a/client/src/test-data.ts b/client/src/test-data.ts
index a7f29cf4c967d6cb400e2c876f96558c6f2eb255..40464d8233914649c8fddb66f59687f6158ef2cd 100644
--- a/client/src/test-data.ts
+++ b/client/src/test-data.ts
@@ -60,11 +60,6 @@ export const INSTANCE_LIST: Instance[] = [
         design_background_color: 'darker green',
         design_logo: 'path/to/logo',
         design_favicon: 'path/to/favicon',
-        home_component: 'HomeComponent',
-        home_component_config: {
-            home_component_text: 'Description',
-            home_component_logo: 'path/to/logo'
-        },
         samp_enabled: true,
         back_to_portal: true,
         search_by_criteria_allowed: false,
@@ -93,11 +88,6 @@ export const INSTANCE_LIST: Instance[] = [
         design_background_color: 'darker green',
         design_logo: 'path/to/logo',
         design_favicon: 'path/to/favicon',
-        home_component: 'HomeComponent',
-        home_component_config: {
-            home_component_text: 'Description',
-            home_component_logo: 'path/to/logo'
-        },
         samp_enabled: true,
         back_to_portal: true,
         search_by_criteria_allowed: false,
@@ -128,11 +118,6 @@ export const INSTANCE: Instance = {
     design_background_color: 'darker green',
     design_logo: '/path/to/logo',
     design_favicon: '/path/to/favicon',
-    home_component: 'HomeComponent',
-    home_component_config: {
-        home_component_text: 'Description',
-        home_component_logo: '/path/to/logo'
-    },
     samp_enabled: true,
     back_to_portal: true,
     search_by_criteria_allowed: false,
diff --git a/client/yarn.lock b/client/yarn.lock
index 6418171403d03c5fd92b58ce44d9bfc89db43592..4962349063a3ce88930ccb6ab09f2edd10e40ac9 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -1928,6 +1928,14 @@
   dependencies:
     "@sinonjs/commons" "^1.7.0"
 
+"@tinymce/tinymce-angular@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@tinymce/tinymce-angular/-/tinymce-angular-6.0.1.tgz#8573bef54be8533c29e938c8c96cb2057e08b7a6"
+  integrity sha512-USuwQwcBmvl1fN9n1FsUqM8ZOQjJLe2VleQRENU9R46ZgrB6Ic5thyUV2RPHUrNgN99QJ+4HyE465qzgv+M7Mw==
+  dependencies:
+    tinymce "^6.0.0 || ^5.5.0"
+    tslib "^2.3.0"
+
 "@tootallnate/once@1":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@@ -6461,6 +6469,13 @@ ngx-bootstrap@^8.0.0:
   dependencies:
     tslib "^2.0.0"
 
+ngx-dynamic-hooks@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/ngx-dynamic-hooks/-/ngx-dynamic-hooks-2.0.3.tgz#1a5559e7df82dfa08484409fb86c06f090b2fedf"
+  integrity sha512-9JNcke0tnUrM4HIWLXqPhw94R5Q61d3F0OdCWh3PNRSf6zsyeIO3Si0HHkUnqRx0mnSX9lxuqdETftcpDfM7nQ==
+  dependencies:
+    tslib "^2.0.0"
+
 ngx-json-viewer@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/ngx-json-viewer/-/ngx-json-viewer-3.0.2.tgz#91e72fe41f80756181aa0d36b4bfaeac5df5b1b1"
@@ -8182,6 +8197,11 @@ thunky@^1.0.2:
   resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
   integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
 
+"tinymce@^6.0.0 || ^5.5.0", tinymce@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.0.3.tgz#993db09afa473a764ad8b594cdaf744b2c7e2e74"
+  integrity sha512-4cu80kWF7nRGhviE10poZtjTkl3jNL+lycilCMfdm3KU5V7FtiQQrKbEo6GInXT05RY78Ha/NFP0gOBELcSpfg==
+
 tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh
index 905a1ca64357249e0bd607c11aea1f94cf6f7040..85345caa90455d1001ccb6a8443c2682d4593b2b 100644
--- a/conf-dev/create-db.sh
+++ b/conf-dev/create-db.sh
@@ -8,7 +8,7 @@ set -e
 curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' --header 'Content-Type: application/json' -X POST http://localhost/database
 
 # Add default instance
-curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","scientific_manager":"M. Durand","instrument":"Multiple","wavelength_domain":"Visible imaging / Spectroscopy","display":10,"data_path":"\/DEFAULT","files_path":"\/INSTANCE_FILES","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"/logo.png","design_favicon":"/favicon.ico","home_component":"WelcomeComponent","home_component_config":{"home_component_text":"AstroNomical Information System","home_component_logo":"/home_component_logo.png"},"samp_enabled":true,"back_to_portal":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance
+curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","scientific_manager":"M. Durand","instrument":"Multiple","wavelength_domain":"Visible imaging / Spectroscopy","display":10,"data_path":"\/DEFAULT","files_path":"\/INSTANCE_FILES","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"/logo.png","design_favicon":"/favicon.ico","samp_enabled":true,"back_to_portal":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance
 
 # Add dataset families
 curl -d '{"label":"Default dataset family","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/instance/default/dataset-family
@@ -109,3 +109,7 @@ curl -d '{"id":12,"name":"burst_id","label":"burst_id","form_label":"Burst ID","
 curl -d '{"id":13,"name":"pipeline_version","label":"pipeline_version","form_label":"Pipeline version","description":null,"primary_key":false,"output_display":130,"criteria_display":130,"search_type":null,"type":"float","operator":null,"dynamic_operator":true,"min":null,"max":null,"placeholder_min":null,"placeholder_max":null,"renderer":null,"renderer_config":null,"selected":false,"order_by":true,"archive":false,"detail":false,"display_detail":130,"renderer_detail":null,"renderer_detail_config":null,"options":null,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":7}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/products/attribute
 curl -d '{"id":14,"name":"schema_version","label":"schema_version","form_label":"Schema version","description":null,"primary_key":false,"output_display":140,"criteria_display":140,"search_type":null,"type":"float","operator":null,"dynamic_operator":true,"min":null,"max":null,"placeholder_min":null,"placeholder_max":null,"renderer":null,"renderer_config":null,"selected":false,"order_by":true,"archive":false,"detail":false,"display_detail":140,"renderer_detail":null,"renderer_detail_config":null,"options":null,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":7}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/products/attribute
 curl -d '{"id":15,"name":"src_id","label":"src_id","form_label":"SRC ID","description":null,"primary_key":false,"output_display":150,"criteria_display":150,"search_type":null,"type":"integer","operator":null,"dynamic_operator":true,"min":null,"max":null,"placeholder_min":null,"placeholder_max":null,"renderer":null,"renderer_config":null,"selected":false,"order_by":true,"archive":false,"detail":false,"display_detail":150,"renderer_detail":null,"renderer_detail_config":null,"options":null,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":7}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/products/attribute
+
+# Add webpages
+curl -d '{"label":"Default","icon":null,"display":10}' --header 'Content-Type: application/json' -X POST http://localhost/instance/default/webpage-family
+curl -d '{"label":"Home","icon":"fas fa-home","display":10,"title":"Home","content":"<div class=\"row align-items-center jumbotron\"><div class=\"col-6 col-md-4 order-md-2 mx-auto text-center\"><img class=\"img-fluid mb-3 mb-md-0\" src=\"http://localhost:8080/instance/default/file-explorer/home_component_logo.png\" alt=\"Instance logo\"></div><div class=\"col-md-8 order-md-1 text-justify pr-md-5\"><h2 class=\"mb-3\">Welcome to the ANIS default instance</h2><p class=\"lead\">This service provides several sub-services to interact with the database.<br>Here is a brief presentation of these sub-services:</p><ul class=\"lead\"><li><a href=\"https://drf-gitlab.cea.fr/svom/sdb/api-import/-/wikis/home\">/import/</a> =&gt; This sub-service allows you to import data L0, L1 or SR3/SR4</li><li>/export-rest =&gt; =&gt; This sub-service allows you to request and export data from the database in json format with a specific URL. To build this URL you could <a class=\"btn btn-warning\" href=\"../../search\">Go to search form</a></li></ul></div></div>"}' --header 'Content-Type: application/json' -X POST http://localhost/webpage-family/1/webpage
diff --git a/server/app/dependencies.php b/server/app/dependencies.php
index 994388f37e4bc123ab59b62f8994d076a30dc551..102f8df534db10df4d9be0832cc6825ba9449115 100644
--- a/server/app/dependencies.php
+++ b/server/app/dependencies.php
@@ -145,6 +145,26 @@ $container->set('App\Action\DatasetAction', function (ContainerInterface $c) {
     return new App\Action\DatasetAction($c->get('em'));
 });
 
+$container->set('App\Action\WebpageFamilyListAction', function (ContainerInterface $c) {
+    return new App\Action\WebpageFamilyListAction($c->get('em'));
+});
+
+$container->set('App\Action\WebpageFamilyAction', function (ContainerInterface $c) {
+    return new App\Action\WebpageFamilyAction($c->get('em'));
+});
+
+$container->set('App\Action\WebpageListAction', function (ContainerInterface $c) {
+    return new App\Action\WebpageListAction($c->get('em'));
+});
+
+$container->set('App\Action\WebpageAction', function (ContainerInterface $c) {
+    return new App\Action\WebpageAction($c->get('em'));
+});
+
+$container->set('App\Action\WebpageListByInstanceAction', function (ContainerInterface $c) {
+    return new App\Action\WebpageListByInstanceAction($c->get('em'));
+});
+
 $container->set('App\Action\CriteriaFamilyListAction', function (ContainerInterface $c) {
     return new App\Action\CriteriaFamilyListAction($c->get('em'));
 });
diff --git a/server/app/routes.php b/server/app/routes.php
index 2b6715908160e51c1ca4dfbd9c8d8e8ac0b44464..6ad263ef5e6d50fe512d28a1bc4a4b2d12eb97db 100644
--- a/server/app/routes.php
+++ b/server/app/routes.php
@@ -40,11 +40,16 @@ $app->group('', function (RouteCollectorProxy $group) {
     $group->map([OPTIONS, GET], '/instance/{name}/file-explorer[{fpath:.*}]', App\Action\InstanceFileExplorerAction::class);
     $group->map([OPTIONS, GET, POST], '/instance/{name}/dataset-group', App\Action\DatasetGroupListAction::class);
     $group->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class);
+    $group->map([OPTIONS, GET, POST], '/instance/{name}/webpage-family', App\Action\WebpageFamilyListAction::class);
     $group->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\DatasetListByInstanceAction::class);
+    $group->map([OPTIONS, GET], '/instance/{name}/webpage', App\Action\WebpageListByInstanceAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/dataset-group/{id}', App\Action\DatasetGroupAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class);
+    $group->map([OPTIONS, GET, PUT, DELETE], '/webpage-family/{id}', App\Action\WebpageFamilyAction::class);
     $group->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class);
+    $group->map([OPTIONS, GET, POST], '/webpage-family/{id}/webpage', App\Action\WebpageListAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
+    $group->map([OPTIONS, GET, PUT, DELETE], '/webpage/{id}', App\Action\WebpageAction::class);
     $group->map([OPTIONS, GET], '/dataset/{name}/file-explorer[{fpath:.*}]', App\Action\DatasetFileExplorerAction::class);
     $group->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\CriteriaFamilyListAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class);
diff --git a/server/doctrine-proxy/__CG__AppEntityInstance.php b/server/doctrine-proxy/__CG__AppEntityInstance.php
index bd2aab261cf8de6bbff87ff7ca87acc15770cc14..a1f2d42bcc6ff779a110dc3fca2b2f4f4875bafb 100644
--- a/server/doctrine-proxy/__CG__AppEntityInstance.php
+++ b/server/doctrine-proxy/__CG__AppEntityInstance.php
@@ -67,10 +67,10 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy
     public function __sleep()
     {
         if ($this->__isInitialized__) {
-            return ['__isInitialized__', 'name', 'label', 'description', 'scientificManager', 'instrument', 'wavelengthDomain', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'backToPortal', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies'];
+            return ['__isInitialized__', 'name', 'label', 'description', 'scientificManager', 'instrument', 'wavelengthDomain', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'sampEnabled', 'backToPortal', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies'];
         }
 
-        return ['__isInitialized__', 'name', 'label', 'description', 'scientificManager', 'instrument', 'wavelengthDomain', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'backToPortal', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies'];
+        return ['__isInitialized__', 'name', 'label', 'description', 'scientificManager', 'instrument', 'wavelengthDomain', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'sampEnabled', 'backToPortal', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies'];
     }
 
     /**
@@ -500,50 +500,6 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy
         return parent::setDesignFavicon($designFavicon);
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function getHomeComponent()
-    {
-
-        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getHomeComponent', []);
-
-        return parent::getHomeComponent();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function setHomeComponent($homeComponent)
-    {
-
-        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setHomeComponent', [$homeComponent]);
-
-        return parent::setHomeComponent($homeComponent);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getHomeComponentConfig()
-    {
-
-        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getHomeComponentConfig', []);
-
-        return parent::getHomeComponentConfig();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function setHomeComponentConfig($homeComponentConfig)
-    {
-
-        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setHomeComponentConfig', [$homeComponentConfig]);
-
-        return parent::setHomeComponentConfig($homeComponentConfig);
-    }
-
     /**
      * {@inheritDoc}
      */
diff --git a/server/doctrine-proxy/__CG__AppEntityWebpage.php b/server/doctrine-proxy/__CG__AppEntityWebpage.php
new file mode 100644
index 0000000000000000000000000000000000000000..45116fc175f6bdcf3675b96510f029606488d871
--- /dev/null
+++ b/server/doctrine-proxy/__CG__AppEntityWebpage.php
@@ -0,0 +1,338 @@
+<?php
+
+namespace DoctrineProxies\__CG__\App\Entity;
+
+
+/**
+ * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR
+ */
+class Webpage extends \App\Entity\Webpage implements \Doctrine\ORM\Proxy\Proxy
+{
+    /**
+     * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with
+     *      three parameters, being respectively the proxy object to be initialized, the method that triggered the
+     *      initialization process and an array of ordered parameters that were passed to that method.
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__setInitializer
+     */
+    public $__initializer__;
+
+    /**
+     * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__setCloner
+     */
+    public $__cloner__;
+
+    /**
+     * @var boolean flag indicating if this object was already initialized
+     *
+     * @see \Doctrine\Persistence\Proxy::__isInitialized
+     */
+    public $__isInitialized__ = false;
+
+    /**
+     * @var array<string, null> properties to be lazy loaded, indexed by property name
+     */
+    public static $lazyPropertiesNames = array (
+);
+
+    /**
+     * @var array<string, mixed> default values of properties to be lazy loaded, with keys being the property names
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties
+     */
+    public static $lazyPropertiesDefaults = array (
+);
+
+
+
+    public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
+    {
+
+        $this->__initializer__ = $initializer;
+        $this->__cloner__      = $cloner;
+    }
+
+
+
+
+
+
+
+    /**
+     * 
+     * @return array
+     */
+    public function __sleep()
+    {
+        if ($this->__isInitialized__) {
+            return ['__isInitialized__', 'id', 'label', 'icon', 'display', 'title', 'content', 'webpageFamily'];
+        }
+
+        return ['__isInitialized__', 'id', 'label', 'icon', 'display', 'title', 'content', 'webpageFamily'];
+    }
+
+    /**
+     * 
+     */
+    public function __wakeup()
+    {
+        if ( ! $this->__isInitialized__) {
+            $this->__initializer__ = function (Webpage $proxy) {
+                $proxy->__setInitializer(null);
+                $proxy->__setCloner(null);
+
+                $existingProperties = get_object_vars($proxy);
+
+                foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) {
+                    if ( ! array_key_exists($property, $existingProperties)) {
+                        $proxy->$property = $defaultValue;
+                    }
+                }
+            };
+
+        }
+    }
+
+    /**
+     * 
+     */
+    public function __clone()
+    {
+        $this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []);
+    }
+
+    /**
+     * Forces initialization of the proxy
+     */
+    public function __load()
+    {
+        $this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __isInitialized()
+    {
+        return $this->__isInitialized__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setInitialized($initialized)
+    {
+        $this->__isInitialized__ = $initialized;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setInitializer(\Closure $initializer = null)
+    {
+        $this->__initializer__ = $initializer;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __getInitializer()
+    {
+        return $this->__initializer__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setCloner(\Closure $cloner = null)
+    {
+        $this->__cloner__ = $cloner;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific cloning logic
+     */
+    public function __getCloner()
+    {
+        return $this->__cloner__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     * @deprecated no longer in use - generated code now relies on internal components rather than generated public API
+     * @static
+     */
+    public function __getLazyProperties()
+    {
+        return self::$lazyPropertiesDefaults;
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    public function getId()
+    {
+        if ($this->__isInitialized__ === false) {
+            return (int)  parent::getId();
+        }
+
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);
+
+        return parent::getId();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getLabel()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getLabel', []);
+
+        return parent::getLabel();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setLabel($label)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setLabel', [$label]);
+
+        return parent::setLabel($label);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getIcon()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getIcon', []);
+
+        return parent::getIcon();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setIcon($icon)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setIcon', [$icon]);
+
+        return parent::setIcon($icon);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getDisplay()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDisplay', []);
+
+        return parent::getDisplay();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setDisplay($display)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDisplay', [$display]);
+
+        return parent::setDisplay($display);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getTitle()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getTitle', []);
+
+        return parent::getTitle();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setTitle($title)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setTitle', [$title]);
+
+        return parent::setTitle($title);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getContent()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getContent', []);
+
+        return parent::getContent();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setContent($content)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setContent', [$content]);
+
+        return parent::setContent($content);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getWebpageFamily()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getWebpageFamily', []);
+
+        return parent::getWebpageFamily();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setWebpageFamily($webpageFamily)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setWebpageFamily', [$webpageFamily]);
+
+        return parent::setWebpageFamily($webpageFamily);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function jsonSerialize(): array
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'jsonSerialize', []);
+
+        return parent::jsonSerialize();
+    }
+
+}
diff --git a/server/doctrine-proxy/__CG__AppEntityWebpageFamily.php b/server/doctrine-proxy/__CG__AppEntityWebpageFamily.php
new file mode 100644
index 0000000000000000000000000000000000000000..5a5932356c40ec5c854db947736705effbbf3cf7
--- /dev/null
+++ b/server/doctrine-proxy/__CG__AppEntityWebpageFamily.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace DoctrineProxies\__CG__\App\Entity;
+
+
+/**
+ * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR
+ */
+class WebpageFamily extends \App\Entity\WebpageFamily implements \Doctrine\ORM\Proxy\Proxy
+{
+    /**
+     * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with
+     *      three parameters, being respectively the proxy object to be initialized, the method that triggered the
+     *      initialization process and an array of ordered parameters that were passed to that method.
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__setInitializer
+     */
+    public $__initializer__;
+
+    /**
+     * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__setCloner
+     */
+    public $__cloner__;
+
+    /**
+     * @var boolean flag indicating if this object was already initialized
+     *
+     * @see \Doctrine\Persistence\Proxy::__isInitialized
+     */
+    public $__isInitialized__ = false;
+
+    /**
+     * @var array<string, null> properties to be lazy loaded, indexed by property name
+     */
+    public static $lazyPropertiesNames = array (
+);
+
+    /**
+     * @var array<string, mixed> default values of properties to be lazy loaded, with keys being the property names
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties
+     */
+    public static $lazyPropertiesDefaults = array (
+);
+
+
+
+    public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
+    {
+
+        $this->__initializer__ = $initializer;
+        $this->__cloner__      = $cloner;
+    }
+
+
+
+
+
+
+
+    /**
+     * 
+     * @return array
+     */
+    public function __sleep()
+    {
+        if ($this->__isInitialized__) {
+            return ['__isInitialized__', 'id', 'label', 'icon', 'display', 'instance'];
+        }
+
+        return ['__isInitialized__', 'id', 'label', 'icon', 'display', 'instance'];
+    }
+
+    /**
+     * 
+     */
+    public function __wakeup()
+    {
+        if ( ! $this->__isInitialized__) {
+            $this->__initializer__ = function (WebpageFamily $proxy) {
+                $proxy->__setInitializer(null);
+                $proxy->__setCloner(null);
+
+                $existingProperties = get_object_vars($proxy);
+
+                foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) {
+                    if ( ! array_key_exists($property, $existingProperties)) {
+                        $proxy->$property = $defaultValue;
+                    }
+                }
+            };
+
+        }
+    }
+
+    /**
+     * 
+     */
+    public function __clone()
+    {
+        $this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []);
+    }
+
+    /**
+     * Forces initialization of the proxy
+     */
+    public function __load()
+    {
+        $this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __isInitialized()
+    {
+        return $this->__isInitialized__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setInitialized($initialized)
+    {
+        $this->__isInitialized__ = $initialized;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setInitializer(\Closure $initializer = null)
+    {
+        $this->__initializer__ = $initializer;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __getInitializer()
+    {
+        return $this->__initializer__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setCloner(\Closure $cloner = null)
+    {
+        $this->__cloner__ = $cloner;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific cloning logic
+     */
+    public function __getCloner()
+    {
+        return $this->__cloner__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     * @deprecated no longer in use - generated code now relies on internal components rather than generated public API
+     * @static
+     */
+    public function __getLazyProperties()
+    {
+        return self::$lazyPropertiesDefaults;
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    public function getId()
+    {
+        if ($this->__isInitialized__ === false) {
+            return (int)  parent::getId();
+        }
+
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);
+
+        return parent::getId();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getLabel()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getLabel', []);
+
+        return parent::getLabel();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setLabel($label)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setLabel', [$label]);
+
+        return parent::setLabel($label);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getIcon()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getIcon', []);
+
+        return parent::getIcon();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setIcon($icon)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setIcon', [$icon]);
+
+        return parent::setIcon($icon);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getDisplay()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDisplay', []);
+
+        return parent::getDisplay();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setDisplay($display)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDisplay', [$display]);
+
+        return parent::setDisplay($display);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function jsonSerialize(): array
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'jsonSerialize', []);
+
+        return parent::jsonSerialize();
+    }
+
+}
diff --git a/server/src/Action/InstanceAction.php b/server/src/Action/InstanceAction.php
index 658c17e505c91c046d70613f33d44ee53deeb80f..26b7dca1b7b483c3e68ed6bcd52e04824a3b5675 100644
--- a/server/src/Action/InstanceAction.php
+++ b/server/src/Action/InstanceAction.php
@@ -78,8 +78,6 @@ final class InstanceAction extends AbstractAction
                 'design_background_color',
                 'design_logo',
                 'design_favicon',
-                'home_component',
-                'home_component_config',
                 'samp_enabled',
                 'back_to_portal',
                 'search_by_criteria_allowed',
@@ -138,8 +136,6 @@ final class InstanceAction extends AbstractAction
         $instance->setDesignBackgroundColor($parsedBody['design_background_color']);
         $instance->setDesignLogo($parsedBody['design_logo']);
         $instance->setDesignFavicon($parsedBody['design_favicon']);
-        $instance->setHomeComponent($parsedBody['home_component']);
-        $instance->setHomeComponentConfig($parsedBody['home_component_config']);
         $instance->setSampEnabled($parsedBody['samp_enabled']);
         $instance->setBackToPortal($parsedBody['back_to_portal']);
         $instance->setSearchByCriteriaAllowed($parsedBody['search_by_criteria_allowed']);
diff --git a/server/src/Action/InstanceListAction.php b/server/src/Action/InstanceListAction.php
index 2cf086edd5736a3ed630a371233db76242abea3d..dce371131a1163c8d651040eaefdea9e12fcfa34 100644
--- a/server/src/Action/InstanceListAction.php
+++ b/server/src/Action/InstanceListAction.php
@@ -78,8 +78,6 @@ final class InstanceListAction extends AbstractAction
                 'design_background_color',
                 'design_logo',
                 'design_favicon',
-                'home_component',
-                'home_component_config',
                 'samp_enabled',
                 'back_to_portal',
                 'search_by_criteria_allowed',
@@ -132,8 +130,6 @@ final class InstanceListAction extends AbstractAction
         $instance->setDesignBackgroundColor($parsedBody['design_background_color']);
         $instance->setDesignLogo($parsedBody['design_logo']);
         $instance->setDesignFavicon($parsedBody['design_favicon']);
-        $instance->setHomeComponent($parsedBody['home_component']);
-        $instance->setHomeComponentConfig($parsedBody['home_component_config']);
         $instance->setSampEnabled($parsedBody['samp_enabled']);
         $instance->setBackToPortal($parsedBody['back_to_portal']);
         $instance->setSearchByCriteriaAllowed($parsedBody['search_by_criteria_allowed']);
diff --git a/server/src/Action/WebpageAction.php b/server/src/Action/WebpageAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ecd704f93d720d030e44d1dc42c648aa9ecbf18
--- /dev/null
+++ b/server/src/Action/WebpageAction.php
@@ -0,0 +1,107 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Webpage;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class WebpageAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the webpage found
+     * `PUT` Full update the webpage and returns the new version
+     * `DELETE` Delete the webpage found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        array $args
+    ): ResponseInterface {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct webpage with primary key
+        $webpage = $this->em->find('App\Entity\Webpage', $args['id']);
+
+        // If webpage is not found 404
+        if (is_null($webpage)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Webpage with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($webpage);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array('label', 'icon', 'display', 'title', 'content');
+            foreach ($fields as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the webpage'
+                    );
+                }
+            }
+
+            $this->editWebpage($webpage, $parsedBody);
+            $payload = json_encode($webpage);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $webpage->getId();
+            $this->em->remove($webpage);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => 'Webpage with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update webpage object with setters
+     *
+     * @param Webpage $webpage    The webpage to update
+     * @param array   $parsedBody Contains the new values ​​of the webpage sent by the user
+     */
+    private function editWebpage(Webpage $webpage, array $parsedBody): void
+    {
+        $webpage->setLabel($parsedBody['label']);
+        $webpage->setIcon($parsedBody['icon']);
+        $webpage->setDisplay($parsedBody['display']);
+        $webpage->setTitle($parsedBody['title']);
+        $webpage->setContent($parsedBody['content']);
+        $this->em->flush();
+    }
+}
diff --git a/server/src/Action/WebpageFamilyAction.php b/server/src/Action/WebpageFamilyAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..e84c397bc547e65a3fcde052353e1cd8819b7a5d
--- /dev/null
+++ b/server/src/Action/WebpageFamilyAction.php
@@ -0,0 +1,105 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\WebpageFamily;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class WebpageFamilyAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the webpage family found
+     * `PUT` Full update the webpage family and returns the new version
+     * `DELETE` Delete the webpage family found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        array $args
+    ): ResponseInterface {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct webpage family with primary key
+        $webpageFamily = $this->em->find('App\Entity\WebpageFamily', $args['id']);
+
+        // If webpage family is not found 404
+        if (is_null($webpageFamily)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Webpage family with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($webpageFamily);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array('label', 'icon', 'display');
+            foreach ($fields as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the webpage family'
+                    );
+                }
+            }
+
+            $this->editWebpageFamily($webpageFamily, $parsedBody);
+            $payload = json_encode($webpageFamily);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $webpageFamily->getId();
+            $this->em->remove($webpageFamily);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => 'Webpage family with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update webpage family object with setters
+     *
+     * @param WebpageFamily $family     The webpage family to update
+     * @param array         $parsedBody Contains the new values ​​of the webpage family sent by the user
+     */
+    private function editWebpageFamily(WebpageFamily $family, array $parsedBody): void
+    {
+        $family->setLabel($parsedBody['label']);
+        $family->setIcon($parsedBody['icon']);
+        $family->setDisplay($parsedBody['display']);
+        $this->em->flush();
+    }
+}
diff --git a/server/src/Action/WebpageFamilyListAction.php b/server/src/Action/WebpageFamilyListAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..20b0cf81d8956a063cdb1b33e29390a128ce4470
--- /dev/null
+++ b/server/src/Action/WebpageFamilyListAction.php
@@ -0,0 +1,104 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Instance;
+use App\Entity\WebpageFamily;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class WebpageFamilyListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all webpage family for a given instance
+     * `POST` Add a new webpage family to a given instance
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        array $args
+    ): ResponseInterface {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+
+        // Returns HTTP 404 if the instance is not found
+        if (is_null($instance)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Instance with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $families = $this->em->getRepository('App\Entity\WebpageFamily')->findBy(array('instance' => $instance));
+            $payload = json_encode($families);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information
+            foreach (array('label', 'icon', 'display') as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new webpage family'
+                    );
+                }
+            }
+
+            $family = $this->postWebpageFamily($parsedBody, $instance);
+            $payload = json_encode($family);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Add a new webpage family into the metamodel
+     *
+     * @param array    $parsedBody Contains the values ​​of the new webpage family sent by the user
+     * @param Instance $instance   The instance for adding the webpage family
+     *
+     * @return WebpageFamily
+     */
+    private function postWebpageFamily(array $parsedBody, Instance $instance): WebpageFamily
+    {
+        $family = new WebpageFamily($instance);
+        $family->setLabel($parsedBody['label']);
+        $family->setIcon($parsedBody['icon']);
+        $family->setDisplay($parsedBody['display']);
+
+        $this->em->persist($family);
+        $this->em->flush();
+
+        return $family;
+    }
+}
diff --git a/server/src/Action/WebpageListAction.php b/server/src/Action/WebpageListAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..71d9d8600bb8609cd1fb49f97591e8c306a84b8d
--- /dev/null
+++ b/server/src/Action/WebpageListAction.php
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\WebpageFamily;
+use App\Entity\Webpage;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class WebpageListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all webpages for a given webpage family
+     * `POST` Add a new webpage
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        array $args
+    ): ResponseInterface {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        $webpageFamily = $this->em->find('App\Entity\WebpageFamily', $args['id']);
+
+        // Returns HTTP 404 if the dataset family is not found
+        if (is_null($webpageFamily)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Webpage family with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $webpages = $this->em->getRepository('App\Entity\Webpage')->findBy(
+                array('webpageFamily' => $webpageFamily)
+            );
+            $payload = json_encode($webpages);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information
+            foreach (array('label', 'icon', 'display', 'title', 'content') as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new webpage'
+                    );
+                }
+            }
+
+            $webpage = $this->postWebpage($parsedBody, $webpageFamily);
+            $payload = json_encode($webpage);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Add a new webpage into the metamodel
+     *
+     * @param array         $parsedBody    Contains the values ​​of the new webpage sent by the user
+     * @param WebpageFamily $webpageFamily The wbepage family for adding the webpage
+     *
+     * @return Webpage
+     */
+    private function postWebpage(array $parsedBody, WebpageFamily $webpageFamily): Webpage
+    {
+        $webpage = new Webpage();
+        $webpage->setLabel($parsedBody['label']);
+        $webpage->setIcon($parsedBody['icon']);
+        $webpage->setDisplay($parsedBody['display']);
+        $webpage->setTitle($parsedBody['title']);
+        $webpage->setContent($parsedBody['content']);
+        $webpage->setWebpageFamily($webpageFamily);
+
+        $this->em->persist($webpage);
+        $this->em->flush();
+
+        return $webpage;
+    }
+}
diff --git a/server/src/Action/WebpageListByInstanceAction.php b/server/src/Action/WebpageListByInstanceAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..bcd06714bee4244c558821d5840e6787ea4f98a2
--- /dev/null
+++ b/server/src/Action/WebpageListByInstanceAction.php
@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Doctrine\ORM\EntityManagerInterface;
+use Slim\Exception\HttpNotFoundException;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class WebpageListByInstanceAction extends AbstractAction
+{
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em Doctrine Entity Manager Interface
+     */
+    public function __construct(EntityManagerInterface $em)
+    {
+        parent::__construct($em);
+    }
+
+    /**
+     * `GET`  Returns a list of all webpages for a given instance
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        array $args
+    ): ResponseInterface {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($instance)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Instance with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $qb = $this->em->createQueryBuilder();
+            $qb->select('w')
+                ->from('App\Entity\Webpage', 'w')
+                ->join('w.webpageFamily', 'f')
+                ->where($qb->expr()->eq('IDENTITY(f.instance)', ':instanceName'));
+
+            $qb->setParameter('instanceName', $instance->getName());
+            $webpages = $qb->getQuery()->getResult();
+            $payload = json_encode($webpages);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/server/src/Entity/Instance.php b/server/src/Entity/Instance.php
index 88f42af0d4cc230389286800040de3c2af4ba467..3c43b7787b9880380ca3234ddeed46d3e166b26f 100644
--- a/server/src/Entity/Instance.php
+++ b/server/src/Entity/Instance.php
@@ -129,20 +129,6 @@ class Instance implements \JsonSerializable
      */
     protected $designFavicon;
 
-    /**
-     * @var string
-     *
-     * @Column(type="string", name="home_component", nullable=true)
-     */
-    protected $homeComponent;
-
-    /**
-     * @var array
-     *
-     * @Column(type="json", name="home_component_config", nullable=true)
-     */
-    protected $homeComponentConfig;
-
     /**
      * @var bool
      *
@@ -365,26 +351,6 @@ class Instance implements \JsonSerializable
         $this->designFavicon = $designFavicon;
     }
 
-    public function getHomeComponent()
-    {
-        return $this->homeComponent;
-    }
-
-    public function setHomeComponent($homeComponent)
-    {
-        $this->homeComponent = $homeComponent;
-    }
-
-    public function getHomeComponentConfig()
-    {
-        return $this->homeComponentConfig;
-    }
-
-    public function setHomeComponentConfig($homeComponentConfig)
-    {
-        $this->homeComponentConfig = $homeComponentConfig;
-    }
-
     public function getSampEnabled()
     {
         return $this->sampEnabled;
@@ -507,8 +473,6 @@ class Instance implements \JsonSerializable
             'design_background_color' => $this->getDesignBackgroundColor(),
             'design_logo' => $this->getDesignLogo(),
             'design_favicon' => $this->getDesignFavicon(),
-            'home_component' => $this->getHomeComponent(),
-            'home_component_config' => $this->getHomeComponentConfig(),
             'samp_enabled' => $this->getSampEnabled(),
             'back_to_portal' => $this->getBackToPortal(),
             'search_by_criteria_allowed' => $this->getSearchByCriteriaAllowed(),
diff --git a/server/src/Entity/Webpage.php b/server/src/Entity/Webpage.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd17832a77ba89435624d95c234e4d0708d41e47
--- /dev/null
+++ b/server/src/Entity/Webpage.php
@@ -0,0 +1,153 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Entity;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Entity
+ *
+ * @Entity
+ * @Table(name="webpage")
+ */
+class Webpage implements \JsonSerializable
+{
+    /**
+     * @var int
+     *
+     * @Id
+     * @Column(type="integer", nullable=false)
+     * @GeneratedValue
+     */
+    protected $id;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", name="label", nullable=false)
+     */
+    protected $label;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", nullable=true)
+     */
+    protected $icon;
+
+    /**
+     * @var int
+     *
+     * @Column(type="integer", name="display", nullable=false)
+     */
+    protected $display;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", name="title", nullable=false)
+     */
+    protected $title;
+
+    /**
+     * @var string
+     *
+     * @Column(type="text", name="content", nullable=false)
+     */
+    protected $content;
+
+    /**
+     * @var WebpageFamily
+     *
+     * @ManyToOne(targetEntity="WebpageFamily")
+     * @JoinColumn(name="id_webpage_family", referencedColumnName="id", nullable=false, onDelete="CASCADE")
+     */
+    protected $webpageFamily;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getLabel()
+    {
+        return $this->label;
+    }
+
+    public function setLabel($label)
+    {
+        $this->label = $label;
+    }
+
+    public function getIcon()
+    {
+        return $this->icon;
+    }
+
+    public function setIcon($icon)
+    {
+        $this->icon = $icon;
+    }
+
+    public function getDisplay()
+    {
+        return $this->display;
+    }
+
+    public function setDisplay($display)
+    {
+        $this->display = $display;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getContent()
+    {
+        return $this->content;
+    }
+
+    public function setContent($content)
+    {
+        $this->content = $content;
+    }
+
+    public function getWebpageFamily()
+    {
+        return $this->webpageFamily;
+    }
+
+    public function setWebpageFamily($webpageFamily)
+    {
+        $this->webpageFamily = $webpageFamily;
+    }
+
+    public function jsonSerialize(): array
+    {
+        return [
+            'id' => $this->getId(),
+            'label' => $this->getLabel(),
+            'icon' => $this->getIcon(),
+            'display' => $this->getDisplay(),
+            'title' => $this->getTitle(),
+            'content'=> $this->getContent(),
+            'id_webpage_family' => $this->getWebpageFamily()->getId(),
+        ];
+    }
+}
diff --git a/server/src/Entity/WebpageFamily.php b/server/src/Entity/WebpageFamily.php
new file mode 100644
index 0000000000000000000000000000000000000000..e12b58399ca774f5da5f0e344adffb1c4b432aa8
--- /dev/null
+++ b/server/src/Entity/WebpageFamily.php
@@ -0,0 +1,111 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Entity;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Entity
+ *
+ * @Entity
+ * @Table(name="webpage_family")
+ */
+class WebpageFamily implements \JsonSerializable
+{
+    /**
+     * @var int
+     *
+     * @Id
+     * @Column(type="integer", nullable=false)
+     * @GeneratedValue
+     */
+    protected $id;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", nullable=false)
+     */
+    protected $label;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", nullable=true)
+     */
+    protected $icon;
+
+    /**
+     * @var int
+     *
+     * @Column(type="integer", nullable=false)
+     */
+    protected $display;
+
+    /**
+     * @var Instance
+     *
+     * @ManyToOne(targetEntity="Instance")
+     * @JoinColumn(name="instance_name", referencedColumnName="name", nullable=false, onDelete="CASCADE")
+     */
+    protected $instance;
+
+    public function __construct(Instance $instance)
+    {
+        $this->instance = $instance;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getLabel()
+    {
+        return $this->label;
+    }
+
+    public function setLabel($label)
+    {
+        $this->label = $label;
+    }
+
+    public function getIcon()
+    {
+        return $this->icon;
+    }
+
+    public function setIcon($icon)
+    {
+        $this->icon = $icon;
+    }
+
+    public function getDisplay()
+    {
+        return $this->display;
+    }
+
+    public function setDisplay($display)
+    {
+        $this->display = $display;
+    }
+
+    public function jsonSerialize(): array
+    {
+        return [
+            'id' => $this->getId(),
+            'label' => $this->getLabel(),
+            'icon' => $this->getIcon(),
+            'display' => $this->getDisplay()
+        ];
+    }
+}