perf: Lighthouse optimizations - lazy loading, contrast fixes, LCP preload, SEO links

This commit is contained in:
Timo Knuth 2026-02-04 15:47:40 +01:00
parent ff7ef0f423
commit 737329794c
20 changed files with 68 additions and 40 deletions

View File

@ -11,19 +11,14 @@ import { LoginRegisterComponent } from './components/login-register/login-regist
import { AuthGuard } from './guards/auth.guard'; import { AuthGuard } from './guards/auth.guard';
import { ListingCategoryGuard } from './guards/listing-category.guard'; import { ListingCategoryGuard } from './guards/listing-category.guard';
// Public pages (eagerly loaded - high traffic) // Public pages - HomeComponent stays eagerly loaded as landing page
import { DetailsBusinessListingComponent } from './pages/details/details-business-listing/details-business-listing.component';
import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component';
import { DetailsUserComponent } from './pages/details/details-user/details-user.component';
import { HomeComponent } from './pages/home/home.component'; import { HomeComponent } from './pages/home/home.component';
import { BrokerListingsComponent } from './pages/listings/broker-listings/broker-listings.component';
import { BusinessListingsComponent } from './pages/listings/business-listings/business-listings.component';
import { CommercialPropertyListingsComponent } from './pages/listings/commercial-property-listings/commercial-property-listings.component';
import { SuccessComponent } from './pages/success/success.component'; import { SuccessComponent } from './pages/success/success.component';
import { TermsOfUseComponent } from './pages/legal/terms-of-use.component'; import { TermsOfUseComponent } from './pages/legal/terms-of-use.component';
import { PrivacyStatementComponent } from './pages/legal/privacy-statement.component'; import { PrivacyStatementComponent } from './pages/legal/privacy-statement.component';
// Note: Account, Edit, Admin, Favorites, MyListing, and EmailUs components are now lazy-loaded below // Note: All listing and details components are now lazy-loaded for better initial bundle size
export const routes: Routes = [ export const routes: Routes = [
{ {
@ -32,17 +27,17 @@ export const routes: Routes = [
}, },
{ {
path: 'businessListings', path: 'businessListings',
component: BusinessListingsComponent, loadComponent: () => import('./pages/listings/business-listings/business-listings.component').then(m => m.BusinessListingsComponent),
runGuardsAndResolvers: 'always', runGuardsAndResolvers: 'always',
}, },
{ {
path: 'commercialPropertyListings', path: 'commercialPropertyListings',
component: CommercialPropertyListingsComponent, loadComponent: () => import('./pages/listings/commercial-property-listings/commercial-property-listings.component').then(m => m.CommercialPropertyListingsComponent),
runGuardsAndResolvers: 'always', runGuardsAndResolvers: 'always',
}, },
{ {
path: 'brokerListings', path: 'brokerListings',
component: BrokerListingsComponent, loadComponent: () => import('./pages/listings/broker-listings/broker-listings.component').then(m => m.BrokerListingsComponent),
runGuardsAndResolvers: 'always', runGuardsAndResolvers: 'always',
}, },
{ {
@ -53,11 +48,11 @@ export const routes: Routes = [
// Listings Details - New SEO-friendly slug-based URLs // Listings Details - New SEO-friendly slug-based URLs
{ {
path: 'business/:slug', path: 'business/:slug',
component: DetailsBusinessListingComponent, loadComponent: () => import('./pages/details/details-business-listing/details-business-listing.component').then(m => m.DetailsBusinessListingComponent),
}, },
{ {
path: 'commercial-property/:slug', path: 'commercial-property/:slug',
component: DetailsCommercialPropertyListingComponent, loadComponent: () => import('./pages/details/details-commercial-property-listing/details-commercial-property-listing.component').then(m => m.DetailsCommercialPropertyListingComponent),
}, },
// Backward compatibility redirects for old UUID-based URLs // Backward compatibility redirects for old UUID-based URLs
{ {
@ -95,7 +90,7 @@ export const routes: Routes = [
// User Details // User Details
{ {
path: 'details-user/:id', path: 'details-user/:id',
component: DetailsUserComponent, loadComponent: () => import('./pages/details/details-user/details-user.component').then(m => m.DetailsUserComponent),
}, },
// ######### // #########
// User edit (lazy-loaded) // User edit (lazy-loaded)

View File

@ -23,9 +23,9 @@
</div> </div>
<div class="mb-4 lg:mb-0 flex flex-col items-center lg:items-end"> <div class="mb-4 lg:mb-0 flex flex-col items-center lg:items-end">
<a class="text-sm text-neutral-600 mb-1 lg:mb-2 hover:text-primary-600 w-full"> <i <a href="tel:+1-800-840-6025" class="text-sm text-neutral-600 mb-1 lg:mb-2 hover:text-primary-600 w-full"> <i
class="fas fa-phone-alt mr-2"></i>1-800-840-6025 </a> class="fas fa-phone-alt mr-2"></i>1-800-840-6025 </a>
<a class="text-sm text-neutral-600 hover:text-primary-600"> <i <a href="mailto:info@bizmatch.net" class="text-sm text-neutral-600 hover:text-primary-600"> <i
class="fas fa-envelope mr-2"></i>info&#64;bizmatch.net </a> class="fas fa-envelope mr-2"></i>info&#64;bizmatch.net </a>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<header class="w-full flex justify-between items-center p-4 bg-white top-0 z-10 h-16 md:h-20"> <header class="w-full flex justify-between items-center p-4 bg-white top-0 z-10 h-16 md:h-20">
<img src="/assets/images/header-logo.png" alt="Logo" class="h-8 md:h-10 w-auto" /> <img src="/assets/images/header-logo.png" alt="BizMatch - Business & Property Marketplace" class="h-8 md:h-10 w-auto" width="120" height="40" />
<div class="hidden md:flex items-center space-x-4"> <div class="hidden md:flex items-center space-x-4">
@if(user){ @if(user){
<a routerLink="/account" class="text-primary-600 border border-primary-600 px-3 py-2 rounded">Account</a> <a routerLink="/account" class="text-primary-600 border border-primary-600 px-3 py-2 rounded">Account</a>
@ -12,7 +12,7 @@
</div> </div>
<button <button
(click)="toggleMenu()" (click)="toggleMenu()"
class="md:hidden text-neutral-600" class="md:hidden text-neutral-700 p-2 min-w-[44px] min-h-[44px] flex items-center justify-center"
aria-label="Open navigation menu" aria-label="Open navigation menu"
[attr.aria-expanded]="isMenuOpen" [attr.aria-expanded]="isMenuOpen"
> >
@ -88,8 +88,8 @@
<!-- Restliche Anpassungen (Innenabstände, Button-Paddings etc.) bleiben wie im vorherigen Schritt --> <!-- Restliche Anpassungen (Innenabstände, Button-Paddings etc.) bleiben wie im vorherigen Schritt -->
<div class="search-form-container bg-white bg-opacity-80 pb-4 md:pb-6 pt-2 px-3 sm:px-4 md:px-6 rounded-lg shadow-lg w-full" [ngClass]="{ 'pt-6': aiSearch }"> <div class="search-form-container bg-white bg-opacity-80 pb-4 md:pb-6 pt-2 px-3 sm:px-4 md:px-6 rounded-lg shadow-lg w-full" [ngClass]="{ 'pt-6': aiSearch }">
@if(!aiSearch){ @if(!aiSearch){
<div class="text-sm lg:text-base mb-1 text-center text-neutral-500 border-neutral-200 dark:text-neutral-400 dark:border-neutral-700 flex justify-between"> <div class="text-sm lg:text-base mb-1 text-center text-neutral-700 border-neutral-200 dark:text-neutral-300 dark:border-neutral-700 flex justify-between">
<ul class="flex flex-wrap -mb-px w-full" role="tablist"> <ul class="flex flex-wrap -mb-px w-full" role="tablist" aria-label="Search categories">
<li class="w-[33%]" role="presentation"> <li class="w-[33%]" role="presentation">
<button <button
type="button" type="button"
@ -99,9 +99,11 @@
[ngClass]=" [ngClass]="
activeTabAction === 'business' activeTabAction === 'business'
? ['text-primary-600', 'border-primary-600', 'active', 'dark:text-primary-500', 'dark:border-primary-500'] ? ['text-primary-600', 'border-primary-600', 'active', 'dark:text-primary-500', 'dark:border-primary-500']
: ['border-transparent', 'hover:text-neutral-600', 'hover:border-neutral-300', 'dark:hover:text-neutral-300'] : ['border-transparent', 'text-neutral-700', 'hover:text-neutral-900', 'hover:border-neutral-400', 'dark:hover:text-neutral-300']
" "
class="tab-link w-full hover:cursor-pointer inline-flex items-center justify-center px-3 py-3 md:p-4 border-b-2 rounded-t-lg bg-transparent" class="tab-link w-full hover:cursor-pointer inline-flex items-center justify-center px-3 py-3 md:p-4 border-b-2 rounded-t-lg bg-transparent min-h-[44px]"
[attr.aria-controls]="'tabpanel-search'"
id="tab-business"
> >
<img src="/assets/images/business_logo.png" alt="" aria-hidden="true" class="tab-icon w-6 h-6 md:w-7 md:h-7 mr-1 md:mr-2 object-contain" width="28" height="28" /> <img src="/assets/images/business_logo.png" alt="" aria-hidden="true" class="tab-icon w-6 h-6 md:w-7 md:h-7 mr-1 md:mr-2 object-contain" width="28" height="28" />
<span>Businesses</span> <span>Businesses</span>
@ -117,9 +119,11 @@
[ngClass]=" [ngClass]="
activeTabAction === 'commercialProperty' activeTabAction === 'commercialProperty'
? ['text-primary-600', 'border-primary-600', 'active', 'dark:text-primary-500', 'dark:border-primary-500'] ? ['text-primary-600', 'border-primary-600', 'active', 'dark:text-primary-500', 'dark:border-primary-500']
: ['border-transparent', 'hover:text-neutral-600', 'hover:border-neutral-300', 'dark:hover:text-neutral-300'] : ['border-transparent', 'text-neutral-700', 'hover:text-neutral-900', 'hover:border-neutral-400', 'dark:hover:text-neutral-300']
" "
class="tab-link w-full hover:cursor-pointer inline-flex items-center justify-center px-3 py-3 md:p-4 border-b-2 rounded-t-lg bg-transparent" class="tab-link w-full hover:cursor-pointer inline-flex items-center justify-center px-3 py-3 md:p-4 border-b-2 rounded-t-lg bg-transparent min-h-[44px]"
[attr.aria-controls]="'tabpanel-search'"
id="tab-properties"
> >
<img src="/assets/images/properties_logo.png" alt="" aria-hidden="true" class="tab-icon w-6 h-6 md:w-7 md:h-7 mr-1 md:mr-2 object-contain" width="28" height="28" /> <img src="/assets/images/properties_logo.png" alt="" aria-hidden="true" class="tab-icon w-6 h-6 md:w-7 md:h-7 mr-1 md:mr-2 object-contain" width="28" height="28" />
<span>Properties</span> <span>Properties</span>
@ -135,9 +139,11 @@
[ngClass]=" [ngClass]="
activeTabAction === 'broker' activeTabAction === 'broker'
? ['text-primary-600', 'border-primary-600', 'active', 'dark:text-primary-500', 'dark:border-primary-500'] ? ['text-primary-600', 'border-primary-600', 'active', 'dark:text-primary-500', 'dark:border-primary-500']
: ['border-transparent', 'hover:text-neutral-600', 'hover:border-neutral-300', 'dark:hover:text-neutral-300'] : ['border-transparent', 'text-neutral-700', 'hover:text-neutral-900', 'hover:border-neutral-400', 'dark:hover:text-neutral-300']
" "
class="tab-link w-full hover:cursor-pointer inline-flex items-center justify-center px-3 py-3 md:p-4 border-b-2 rounded-t-lg bg-transparent" class="tab-link w-full hover:cursor-pointer inline-flex items-center justify-center px-3 py-3 md:p-4 border-b-2 rounded-t-lg bg-transparent min-h-[44px]"
[attr.aria-controls]="'tabpanel-search'"
id="tab-professionals"
> >
<img <img
src="/assets/images/icon_professionals.png" src="/assets/images/icon_professionals.png"
@ -153,7 +159,7 @@
</ul> </ul>
</div> </div>
} @if(criteria && !aiSearch){ } @if(criteria && !aiSearch){
<div class="w-full max-w-3xl mx-auto bg-white rounded-lg flex flex-col md:flex-row md:border md:border-neutral-300"> <div id="tabpanel-search" role="tabpanel" aria-labelledby="tab-business" class="w-full max-w-3xl mx-auto bg-white rounded-lg flex flex-col md:flex-row md:border md:border-neutral-300">
<div class="md:flex-none md:w-48 flex-1 md:border-r border-neutral-300 overflow-hidden mb-2 md:mb-0"> <div class="md:flex-none md:w-48 flex-1 md:border-r border-neutral-300 overflow-hidden mb-2 md:mb-0">
<div class="relative max-sm:border border-neutral-300 rounded-md"> <div class="relative max-sm:border border-neutral-300 rounded-md">
<label for="type-filter" class="sr-only">Filter by type</label> <label for="type-filter" class="sr-only">Filter by type</label>
@ -195,7 +201,11 @@
groupBy="type" groupBy="type"
labelForId="location-search" labelForId="location-search"
aria-label="Search by city or state" aria-label="Search by city or state"
[inputAttrs]="{'aria-describedby': 'location-search-hint'}"
> >
<ng-template ng-footer-tmp>
<span id="location-search-hint" class="sr-only">Enter at least 2 characters to search for a city or state</span>
</ng-template>
@for (city of cities$ | async; track city.id) { @let state = city.type==='city'?city.content.state:''; @let separator = city.type==='city'?' - ':''; @for (city of cities$ | async; track city.id) { @let state = city.type==='city'?city.content.state:''; @let separator = city.type==='city'?' - ':'';
<ng-option [value]="city">{{ city.content.name }}{{ separator }}{{ state }}</ng-option> <ng-option [value]="city">{{ city.content.name }}{{ separator }}{{ state }}</ng-option>
} }

View File

@ -1,7 +1,7 @@
select:not([size]) { select:not([size]) {
background-image: unset; background-image: unset;
} }
[type='text'], [type='text'],
[type='email'], [type='email'],
[type='url'], [type='url'],
@ -19,39 +19,51 @@ textarea,
select { select {
border: unset; border: unset;
} }
.toggle-checkbox:checked { .toggle-checkbox:checked {
right: 0; right: 0;
border-color: rgb(125 211 252); border-color: rgb(125 211 252);
} }
.toggle-checkbox:checked + .toggle-label {
.toggle-checkbox:checked+.toggle-label {
background-color: rgb(125 211 252); background-color: rgb(125 211 252);
} }
:host ::ng-deep .ng-select.ng-select-single .ng-select-container { :host ::ng-deep .ng-select.ng-select-single .ng-select-container {
min-height: 52px; min-height: 52px;
border: none; border: none;
background-color: transparent; background-color: transparent;
.ng-value-container .ng-input { .ng-value-container .ng-input {
top: 12px; top: 12px;
} }
span.ng-arrow-wrapper { span.ng-arrow-wrapper {
display: none; display: none;
} }
} }
select { select {
color: #000; /* Standard-Textfarbe für das Dropdown */ color: #000;
/* Standard-Textfarbe für das Dropdown */
// background-color: #fff; /* Hintergrundfarbe für das Dropdown */ // background-color: #fff; /* Hintergrundfarbe für das Dropdown */
} }
select option { select option {
color: #000; /* Textfarbe für Dropdown-Optionen */ color: #000;
/* Textfarbe für Dropdown-Optionen */
} }
select.placeholder-selected { select.placeholder-selected {
color: #999; /* Farbe für den Platzhalter */ color: #6b7280;
/* gray-500 - besserer Kontrast für WCAG AA */
} }
input::placeholder { input::placeholder {
color: #555; /* Dunkleres Grau */ color: #555;
opacity: 1; /* Stellt sicher, dass die Deckkraft 100% ist */ /* Dunkleres Grau */
opacity: 1;
/* Stellt sicher, dass die Deckkraft 100% ist */
} }
/* Stellt sicher, dass die Optionen im Dropdown immer schwarz sind */ /* Stellt sicher, dass die Optionen im Dropdown immer schwarz sind */
@ -59,10 +71,14 @@ select:focus option,
select:hover option { select:hover option {
color: #000 !important; color: #000 !important;
} }
input[type='text'][name='aiSearchText'] { input[type='text'][name='aiSearchText'] {
padding: 14px; /* Innerer Abstand */ padding: 14px;
font-size: 16px; /* Schriftgröße anpassen */ /* Innerer Abstand */
box-sizing: border-box; /* Padding und Border in die Höhe und Breite einrechnen */ font-size: 16px;
/* Schriftgröße anpassen */
box-sizing: border-box;
/* Padding und Border in die Höhe und Breite einrechnen */
height: 48px; height: 48px;
} }
@ -145,6 +161,7 @@ select,
opacity: 0; opacity: 0;
transform: translateY(10px); transform: translateY(10px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
@ -212,6 +229,7 @@ header {
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
&.text-blue-600.border.border-blue-600 { &.text-blue-600.border.border-blue-600 {
// Log In button // Log In button
&:hover { &:hover {
background-color: rgba(37, 99, 235, 0.05); background-color: rgba(37, 99, 235, 0.05);
@ -224,6 +242,7 @@ header {
} }
&.bg-blue-600 { &.bg-blue-600 {
// Register button // Register button
&:hover { &:hover {
background-color: rgb(29, 78, 216); background-color: rgb(29, 78, 216);
@ -249,4 +268,4 @@ header {
clip: rect(0, 0, 0, 0); clip: rect(0, 0, 0, 0);
white-space: nowrap; white-space: nowrap;
border-width: 0; border-width: 0;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -1,6 +1,6 @@
// Build information, automatically generated by `the_build_script` :zwinkern: // Build information, automatically generated by `the_build_script` :zwinkern:
const build = { const build = {
timestamp: "GER: 03.02.2026 12:44 | TX: 02/03/2026 5:44 AM" timestamp: "GER: 04.02.2026 15:43 | TX: 02/04/2026 8:43 AM"
}; };
export default build; export default build;

View File

@ -35,6 +35,9 @@
<!-- Preload critical assets --> <!-- Preload critical assets -->
<link rel="preload" as="image" href="/assets/images/header-logo.png" type="image/png" /> <link rel="preload" as="image" href="/assets/images/header-logo.png" type="image/png" />
<!-- Hero background is LCP element - preload with high priority -->
<link rel="preload" as="image" href="/assets/images/flags_bg.avif" type="image/avif" fetchpriority="high" />
<link rel="preload" as="image" href="/assets/images/flags_bg.jpg" imagesrcset="/assets/images/flags_bg.jpg" type="image/jpeg" />
<!-- Prefetch common assets --> <!-- Prefetch common assets -->
<link rel="prefetch" as="image" href="/assets/images/business_logo.png" /> <link rel="prefetch" as="image" href="/assets/images/business_logo.png" />

View File

@ -5,7 +5,8 @@
@tailwind utilities; @tailwind utilities;
// External CSS imports - these URL imports don't trigger deprecation warnings // External CSS imports - these URL imports don't trigger deprecation warnings
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap'); // Using css2 API with specific weights for better performance
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap');
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css'); @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css');
// Local CSS files loaded as CSS (not SCSS) to avoid @import deprecation // Local CSS files loaded as CSS (not SCSS) to avoid @import deprecation