[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-en-\u002Fblog\u002Fcustomer-portal-\u002Fen\u002Fblog\u002Fcustomer-portal":3,"blog-post-surround-en-\u002Fblog\u002Fcustomer-portal-\u002Fen\u002Fblog\u002Fcustomer-portal":1861,"related-posts-en-\u002Fblog\u002Fcustomer-portal-\u002Fen\u002Fblog\u002Fcustomer-portal":1868},{"id":4,"title":5,"authors":6,"badge":13,"body":15,"categories":1843,"date":1846,"description":1847,"extension":1848,"image":1849,"meta":1851,"navigation":941,"path":1852,"readingTime":457,"seo":1853,"stem":1854,"tags":1855,"__hash__":1860},"posts_en\u002Fblog\u002F1.customer-portal.md","Customer Portal Development: From 6 Months to 6 Weeks",[7],{"name":8,"to":9,"avatar":10,"bio":12},"Marcel Posdijk","https:\u002F\u002Fx.com\u002Fmarcelposdijk",{"src":11},"\u002Fimages\u002Fteam\u002Fmarcel.jpg","Founder and lead developer at Ludulicious B.V. with over 25 years of experience in web development and software architecture.",{"label":14},"Customer Portal",{"type":16,"value":17,"toc":1816},"minimark",[18,23,27,33,52,57,83,94,98,103,106,111,137,141,145,148,315,320,348,354,358,361,669,674,700,704,708,711,882,893,897,900,989,999,1003,1006,1263,1273,1277,1281,1291,1296,1323,1328,1497,1501,1505,1524,1528,1546,1550,1570,1574,1594,1598,1601,1672,1676,1679,1723,1727,1730,1733,1736,1762,1767,1774,1803,1806,1812],[19,20,22],"h2",{"id":21},"the-problem-customer-portal-development-taking-forever","The Problem: Customer Portal Development Taking Forever",[24,25,26],"p",{},"In 2023, we were building customer portals for clients, and every project was taking 6+ months. The same features were being rebuilt from scratch each time, with no reusable patterns or optimized architecture.",[24,28,29],{},[30,31,32],"strong",{},"The Challenge:",[34,35,36,40,43,46,49],"ul",{},[37,38,39],"li",{},"Every portal project started from zero",[37,41,42],{},"Authentication systems rebuilt repeatedly",[37,44,45],{},"No standardized architecture patterns",[37,47,48],{},"Performance issues discovered late in development",[37,50,51],{},"Clients paying premium prices for basic functionality",[24,53,54],{},[30,55,56],{},"The Numbers:",[34,58,59,65,71,77],{},[37,60,61,64],{},[30,62,63],{},"Development Time",": 6+ months per portal",[37,66,67,70],{},[30,68,69],{},"Cost",": €50,000+ per project",[37,72,73,76],{},[30,74,75],{},"Performance",": 2-3 second page load times",[37,78,79,82],{},[30,80,81],{},"Maintenance",": High ongoing costs due to custom code",[24,84,85],{},[86,87],"img",{"alt":88,"className":89,"height":91,"src":92,"width":93},"Customer portal development challenges",[90],"rounded-lg",600,"https:\u002F\u002Fpicsum.photos\u002Fid\u002F3\u002F1000\u002F600",1000,[19,95,97],{"id":96},"the-solution-streamlined-portal-architecture","The Solution: Streamlined Portal Architecture",[99,100,102],"h3",{"id":101},"our-approach-proven-patterns-performance-optimization","Our Approach: Proven Patterns + Performance Optimization",[24,104,105],{},"We developed a standardized approach that combines proven architecture patterns with performance optimization techniques:",[24,107,108],{},[30,109,110],{},"Key Innovations:",[34,112,113,119,125,131],{},[37,114,115,118],{},[30,116,117],{},"Unified Tech Stack",": Single technology stack for all portals",[37,120,121,124],{},[30,122,123],{},"Performance-First Architecture",": Built-in optimization from day one",[37,126,127,130],{},[30,128,129],{},"Reusable Components",": Standardized UI and functionality",[37,132,133,136],{},[30,134,135],{},"Database Optimization",": Leveraging our PostgreSQL expertise",[19,138,140],{"id":139},"the-technical-foundation","The Technical Foundation",[99,142,144],{"id":143},"streamlined-tech-stack","Streamlined Tech Stack",[24,146,147],{},"We simplified our technology choices to focus on performance and maintainability:",[149,150,155],"pre",{"className":151,"code":152,"language":153,"meta":154,"style":154},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Unified tech stack for all customer portals\nconst techStack = {\n  frontend: 'Nuxt 4',           \u002F\u002F Full-stack framework\n  ui: 'Nuxt UI + Tailwind CSS', \u002F\u002F Consistent design system\n  database: 'PostgreSQL',       \u002F\u002F Optimized for performance\n  orm: 'Prisma',               \u002F\u002F Type-safe database access\n  auth: 'better-auth',          \u002F\u002F Modern authentication\n  deployment: 'Docker + Vercel' \u002F\u002F Scalable hosting\n};\n","typescript","",[156,157,158,167,185,211,231,251,271,291,309],"code",{"__ignoreMap":154},[159,160,163],"span",{"class":161,"line":162},"line",1,[159,164,166],{"class":165},"sHwdD","\u002F\u002F Unified tech stack for all customer portals\n",[159,168,170,174,178,182],{"class":161,"line":169},2,[159,171,173],{"class":172},"spNyl","const",[159,175,177],{"class":176},"sTEyZ"," techStack ",[159,179,181],{"class":180},"sMK4o","=",[159,183,184],{"class":180}," {\n",[159,186,188,192,195,198,202,205,208],{"class":161,"line":187},3,[159,189,191],{"class":190},"swJcz","  frontend",[159,193,194],{"class":180},":",[159,196,197],{"class":180}," '",[159,199,201],{"class":200},"sfazB","Nuxt 4",[159,203,204],{"class":180},"'",[159,206,207],{"class":180},",",[159,209,210],{"class":165},"           \u002F\u002F Full-stack framework\n",[159,212,214,217,219,221,224,226,228],{"class":161,"line":213},4,[159,215,216],{"class":190},"  ui",[159,218,194],{"class":180},[159,220,197],{"class":180},[159,222,223],{"class":200},"Nuxt UI + Tailwind CSS",[159,225,204],{"class":180},[159,227,207],{"class":180},[159,229,230],{"class":165}," \u002F\u002F Consistent design system\n",[159,232,234,237,239,241,244,246,248],{"class":161,"line":233},5,[159,235,236],{"class":190},"  database",[159,238,194],{"class":180},[159,240,197],{"class":180},[159,242,243],{"class":200},"PostgreSQL",[159,245,204],{"class":180},[159,247,207],{"class":180},[159,249,250],{"class":165},"       \u002F\u002F Optimized for performance\n",[159,252,254,257,259,261,264,266,268],{"class":161,"line":253},6,[159,255,256],{"class":190},"  orm",[159,258,194],{"class":180},[159,260,197],{"class":180},[159,262,263],{"class":200},"Prisma",[159,265,204],{"class":180},[159,267,207],{"class":180},[159,269,270],{"class":165},"               \u002F\u002F Type-safe database access\n",[159,272,274,277,279,281,284,286,288],{"class":161,"line":273},7,[159,275,276],{"class":190},"  auth",[159,278,194],{"class":180},[159,280,197],{"class":180},[159,282,283],{"class":200},"better-auth",[159,285,204],{"class":180},[159,287,207],{"class":180},[159,289,290],{"class":165},"          \u002F\u002F Modern authentication\n",[159,292,294,297,299,301,304,306],{"class":161,"line":293},8,[159,295,296],{"class":190},"  deployment",[159,298,194],{"class":180},[159,300,197],{"class":180},[159,302,303],{"class":200},"Docker + Vercel",[159,305,204],{"class":180},[159,307,308],{"class":165}," \u002F\u002F Scalable hosting\n",[159,310,312],{"class":161,"line":311},9,[159,313,314],{"class":180},"};\n",[24,316,317],{},[30,318,319],{},"Why This Stack Works:",[34,321,322,327,332,337,342],{},[37,323,324,326],{},[30,325,201],{},": Full-stack framework eliminates backend\u002Ffrontend coordination overhead",[37,328,329,331],{},[30,330,243],{},": Leverages our database optimization expertise for sub-100ms queries",[37,333,334,336],{},[30,335,263],{},": Type-safe database access prevents runtime errors",[37,338,339,341],{},[30,340,283],{},": Modern authentication with built-in security features",[37,343,344,347],{},[30,345,346],{},"Single deployment",": Reduces complexity and maintenance overhead",[24,349,350,353],{},[30,351,352],{},"Result:"," Development time reduced from 6 months to 6 weeks (75% improvement)",[99,355,357],{"id":356},"performance-optimized-architecture","Performance-Optimized Architecture",[24,359,360],{},"Every portal is built with performance optimization from the start:",[149,362,364],{"className":151,"code":363,"language":153,"meta":154,"style":154},"\u002F\u002F Performance-first architecture patterns\nexport default defineNuxtConfig({\n  \u002F\u002F Built-in performance optimizations\n  nitro: {\n    experimental: {\n      wasm: true\n    }\n  },\n  \n  \u002F\u002F Database connection pooling\n  runtimeConfig: {\n    databaseUrl: process.env.DATABASE_URL,\n    redisUrl: process.env.REDIS_URL\n  },\n  \n  \u002F\u002F Caching strategy\n  routeRules: {\n    '\u002Fdashboard': { \n      prerender: true,\n      headers: { 'cache-control': 's-maxage=300' }\n    },\n    '\u002Fapi\u002F**': { \n      cors: true,\n      headers: { 'cache-control': 's-maxage=60' }\n    }\n  }\n});\n",[156,365,366,371,390,395,404,413,424,429,434,439,445,455,480,499,504,509,515,525,544,557,586,592,608,620,646,651,657],{"__ignoreMap":154},[159,367,368],{"class":161,"line":162},[159,369,370],{"class":165},"\u002F\u002F Performance-first architecture patterns\n",[159,372,373,377,380,384,387],{"class":161,"line":169},[159,374,376],{"class":375},"s7zQu","export",[159,378,379],{"class":375}," default",[159,381,383],{"class":382},"s2Zo4"," defineNuxtConfig",[159,385,386],{"class":176},"(",[159,388,389],{"class":180},"{\n",[159,391,392],{"class":161,"line":187},[159,393,394],{"class":165},"  \u002F\u002F Built-in performance optimizations\n",[159,396,397,400,402],{"class":161,"line":213},[159,398,399],{"class":190},"  nitro",[159,401,194],{"class":180},[159,403,184],{"class":180},[159,405,406,409,411],{"class":161,"line":233},[159,407,408],{"class":190},"    experimental",[159,410,194],{"class":180},[159,412,184],{"class":180},[159,414,415,418,420],{"class":161,"line":253},[159,416,417],{"class":190},"      wasm",[159,419,194],{"class":180},[159,421,423],{"class":422},"sfNiH"," true\n",[159,425,426],{"class":161,"line":273},[159,427,428],{"class":180},"    }\n",[159,430,431],{"class":161,"line":293},[159,432,433],{"class":180},"  },\n",[159,435,436],{"class":161,"line":311},[159,437,438],{"class":176},"  \n",[159,440,442],{"class":161,"line":441},10,[159,443,444],{"class":165},"  \u002F\u002F Database connection pooling\n",[159,446,448,451,453],{"class":161,"line":447},11,[159,449,450],{"class":190},"  runtimeConfig",[159,452,194],{"class":180},[159,454,184],{"class":180},[159,456,458,461,463,466,469,472,474,477],{"class":161,"line":457},12,[159,459,460],{"class":190},"    databaseUrl",[159,462,194],{"class":180},[159,464,465],{"class":176}," process",[159,467,468],{"class":180},".",[159,470,471],{"class":176},"env",[159,473,468],{"class":180},[159,475,476],{"class":176},"DATABASE_URL",[159,478,479],{"class":180},",\n",[159,481,483,486,488,490,492,494,496],{"class":161,"line":482},13,[159,484,485],{"class":190},"    redisUrl",[159,487,194],{"class":180},[159,489,465],{"class":176},[159,491,468],{"class":180},[159,493,471],{"class":176},[159,495,468],{"class":180},[159,497,498],{"class":176},"REDIS_URL\n",[159,500,502],{"class":161,"line":501},14,[159,503,433],{"class":180},[159,505,507],{"class":161,"line":506},15,[159,508,438],{"class":176},[159,510,512],{"class":161,"line":511},16,[159,513,514],{"class":165},"  \u002F\u002F Caching strategy\n",[159,516,518,521,523],{"class":161,"line":517},17,[159,519,520],{"class":190},"  routeRules",[159,522,194],{"class":180},[159,524,184],{"class":180},[159,526,528,531,534,536,538,541],{"class":161,"line":527},18,[159,529,530],{"class":180},"    '",[159,532,533],{"class":190},"\u002Fdashboard",[159,535,204],{"class":180},[159,537,194],{"class":180},[159,539,540],{"class":180}," {",[159,542,543],{"class":176}," \n",[159,545,547,550,552,555],{"class":161,"line":546},19,[159,548,549],{"class":190},"      prerender",[159,551,194],{"class":180},[159,553,554],{"class":422}," true",[159,556,479],{"class":180},[159,558,560,563,565,567,569,572,574,576,578,581,583],{"class":161,"line":559},20,[159,561,562],{"class":190},"      headers",[159,564,194],{"class":180},[159,566,540],{"class":180},[159,568,197],{"class":180},[159,570,571],{"class":190},"cache-control",[159,573,204],{"class":180},[159,575,194],{"class":180},[159,577,197],{"class":180},[159,579,580],{"class":200},"s-maxage=300",[159,582,204],{"class":180},[159,584,585],{"class":180}," }\n",[159,587,589],{"class":161,"line":588},21,[159,590,591],{"class":180},"    },\n",[159,593,595,597,600,602,604,606],{"class":161,"line":594},22,[159,596,530],{"class":180},[159,598,599],{"class":190},"\u002Fapi\u002F**",[159,601,204],{"class":180},[159,603,194],{"class":180},[159,605,540],{"class":180},[159,607,543],{"class":176},[159,609,611,614,616,618],{"class":161,"line":610},23,[159,612,613],{"class":190},"      cors",[159,615,194],{"class":180},[159,617,554],{"class":422},[159,619,479],{"class":180},[159,621,623,625,627,629,631,633,635,637,639,642,644],{"class":161,"line":622},24,[159,624,562],{"class":190},[159,626,194],{"class":180},[159,628,540],{"class":180},[159,630,197],{"class":180},[159,632,571],{"class":190},[159,634,204],{"class":180},[159,636,194],{"class":180},[159,638,197],{"class":180},[159,640,641],{"class":200},"s-maxage=60",[159,643,204],{"class":180},[159,645,585],{"class":180},[159,647,649],{"class":161,"line":648},25,[159,650,428],{"class":180},[159,652,654],{"class":161,"line":653},26,[159,655,656],{"class":180},"  }\n",[159,658,660,663,666],{"class":161,"line":659},27,[159,661,662],{"class":180},"}",[159,664,665],{"class":176},")",[159,667,668],{"class":180},";\n",[24,670,671],{},[30,672,673],{},"Performance Results:",[34,675,676,682,688,694],{},[37,677,678,681],{},[30,679,680],{},"Page Load Times",": 200ms average (vs 2-3 seconds before)",[37,683,684,687],{},[30,685,686],{},"Database Queries",": Sub-100ms using our PostgreSQL optimization techniques",[37,689,690,693],{},[30,691,692],{},"Authentication",": Sub-50ms login\u002Flogout operations",[37,695,696,699],{},[30,697,698],{},"API Responses",": Sub-200ms for all endpoints",[19,701,703],{"id":702},"core-portal-components","Core Portal Components",[99,705,707],{"id":706},"_1-authentication-system","1. Authentication System",[24,709,710],{},"Our standardized authentication handles all common scenarios:",[149,712,714],{"className":151,"code":713,"language":153,"meta":154,"style":154},"\u002F\u002F Unified authentication configuration\nexport const authConfig = {\n  providers: {\n    email: {\n      enabled: true,\n      requireEmailVerification: true\n    },\n    google: { enabled: true },\n    microsoft: { enabled: true },\n    github: { enabled: true }\n  },\n  features: {\n    twoFactor: true,\n    passwordReset: true,\n    accountLockout: true\n  }\n};\n",[156,715,716,721,735,744,753,764,773,777,796,813,830,834,843,854,865,874,878],{"__ignoreMap":154},[159,717,718],{"class":161,"line":162},[159,719,720],{"class":165},"\u002F\u002F Unified authentication configuration\n",[159,722,723,725,728,731,733],{"class":161,"line":169},[159,724,376],{"class":375},[159,726,727],{"class":172}," const",[159,729,730],{"class":176}," authConfig ",[159,732,181],{"class":180},[159,734,184],{"class":180},[159,736,737,740,742],{"class":161,"line":187},[159,738,739],{"class":190},"  providers",[159,741,194],{"class":180},[159,743,184],{"class":180},[159,745,746,749,751],{"class":161,"line":213},[159,747,748],{"class":190},"    email",[159,750,194],{"class":180},[159,752,184],{"class":180},[159,754,755,758,760,762],{"class":161,"line":233},[159,756,757],{"class":190},"      enabled",[159,759,194],{"class":180},[159,761,554],{"class":422},[159,763,479],{"class":180},[159,765,766,769,771],{"class":161,"line":253},[159,767,768],{"class":190},"      requireEmailVerification",[159,770,194],{"class":180},[159,772,423],{"class":422},[159,774,775],{"class":161,"line":273},[159,776,591],{"class":180},[159,778,779,782,784,786,789,791,793],{"class":161,"line":293},[159,780,781],{"class":190},"    google",[159,783,194],{"class":180},[159,785,540],{"class":180},[159,787,788],{"class":190}," enabled",[159,790,194],{"class":180},[159,792,554],{"class":422},[159,794,795],{"class":180}," },\n",[159,797,798,801,803,805,807,809,811],{"class":161,"line":311},[159,799,800],{"class":190},"    microsoft",[159,802,194],{"class":180},[159,804,540],{"class":180},[159,806,788],{"class":190},[159,808,194],{"class":180},[159,810,554],{"class":422},[159,812,795],{"class":180},[159,814,815,818,820,822,824,826,828],{"class":161,"line":441},[159,816,817],{"class":190},"    github",[159,819,194],{"class":180},[159,821,540],{"class":180},[159,823,788],{"class":190},[159,825,194],{"class":180},[159,827,554],{"class":422},[159,829,585],{"class":180},[159,831,832],{"class":161,"line":447},[159,833,433],{"class":180},[159,835,836,839,841],{"class":161,"line":457},[159,837,838],{"class":190},"  features",[159,840,194],{"class":180},[159,842,184],{"class":180},[159,844,845,848,850,852],{"class":161,"line":482},[159,846,847],{"class":190},"    twoFactor",[159,849,194],{"class":180},[159,851,554],{"class":422},[159,853,479],{"class":180},[159,855,856,859,861,863],{"class":161,"line":501},[159,857,858],{"class":190},"    passwordReset",[159,860,194],{"class":180},[159,862,554],{"class":422},[159,864,479],{"class":180},[159,866,867,870,872],{"class":161,"line":506},[159,868,869],{"class":190},"    accountLockout",[159,871,194],{"class":180},[159,873,423],{"class":422},[159,875,876],{"class":161,"line":511},[159,877,656],{"class":180},[159,879,880],{"class":161,"line":517},[159,881,314],{"class":180},[24,883,884,887,888,468],{},[30,885,886],{},"Cross-Link to Authentication Article:","\nFor detailed authentication strategies and security best practices, see our ",[889,890,892],"a",{"href":891},"\u002Fblog\u002Fauthentication-strategies","Authentication Strategies Guide",[99,894,896],{"id":895},"_2-database-architecture","2. Database Architecture",[24,898,899],{},"Every portal uses our optimized PostgreSQL setup:",[149,901,905],{"className":902,"code":903,"language":904,"meta":154,"style":154},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- Standardized database schema\nCREATE TABLE clients (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    name VARCHAR(255) NOT NULL,\n    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n);\n\nCREATE TABLE users (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    client_id UUID REFERENCES clients(id),\n    email VARCHAR(255) UNIQUE NOT NULL,\n    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n);\n\n-- Optimized indexes for performance\nCREATE INDEX CONCURRENTLY idx_users_client_email \nON users (client_id, email);\n","sql",[156,906,907,912,917,922,927,932,937,943,948,952,957,962,966,970,974,979,984],{"__ignoreMap":154},[159,908,909],{"class":161,"line":162},[159,910,911],{},"-- Standardized database schema\n",[159,913,914],{"class":161,"line":169},[159,915,916],{},"CREATE TABLE clients (\n",[159,918,919],{"class":161,"line":187},[159,920,921],{},"    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n",[159,923,924],{"class":161,"line":213},[159,925,926],{},"    name VARCHAR(255) NOT NULL,\n",[159,928,929],{"class":161,"line":233},[159,930,931],{},"    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n",[159,933,934],{"class":161,"line":253},[159,935,936],{},");\n",[159,938,939],{"class":161,"line":273},[159,940,942],{"emptyLinePlaceholder":941},true,"\n",[159,944,945],{"class":161,"line":293},[159,946,947],{},"CREATE TABLE users (\n",[159,949,950],{"class":161,"line":311},[159,951,921],{},[159,953,954],{"class":161,"line":441},[159,955,956],{},"    client_id UUID REFERENCES clients(id),\n",[159,958,959],{"class":161,"line":447},[159,960,961],{},"    email VARCHAR(255) UNIQUE NOT NULL,\n",[159,963,964],{"class":161,"line":457},[159,965,931],{},[159,967,968],{"class":161,"line":482},[159,969,936],{},[159,971,972],{"class":161,"line":501},[159,973,942],{"emptyLinePlaceholder":941},[159,975,976],{"class":161,"line":506},[159,977,978],{},"-- Optimized indexes for performance\n",[159,980,981],{"class":161,"line":511},[159,982,983],{},"CREATE INDEX CONCURRENTLY idx_users_client_email \n",[159,985,986],{"class":161,"line":517},[159,987,988],{},"ON users (client_id, email);\n",[24,990,991,994,995,468],{},[30,992,993],{},"Cross-Link to Database Performance:","\nFor database optimization techniques that power our portals, see our ",[889,996,998],{"href":997},"\u002Fblog\u002Fpostgresql-performance-strategy","PostgreSQL Performance Tuning Guide",[99,1000,1002],{"id":1001},"_3-caching-strategy","3. Caching Strategy",[24,1004,1005],{},"Built-in caching ensures optimal performance:",[149,1007,1009],{"className":151,"code":1008,"language":153,"meta":154,"style":154},"\u002F\u002F Multi-layer caching implementation\nexport const usePortalCache = () => {\n  \u002F\u002F Application-level caching\n  const cache = useNuxtApp().$cache;\n  \n  \u002F\u002F Database query caching\n  const getCachedData = async (key: string, fetcher: () => Promise\u003Cany>) => {\n    const cached = await cache.get(key);\n    if (cached) return cached;\n    \n    const data = await fetcher();\n    await cache.set(key, data, 300); \u002F\u002F 5-minute cache\n    return data;\n  };\n  \n  return { getCachedData };\n};\n",[156,1010,1011,1016,1035,1040,1064,1068,1073,1125,1153,1173,1178,1195,1229,1238,1243,1247,1259],{"__ignoreMap":154},[159,1012,1013],{"class":161,"line":162},[159,1014,1015],{"class":165},"\u002F\u002F Multi-layer caching implementation\n",[159,1017,1018,1020,1022,1025,1027,1030,1033],{"class":161,"line":169},[159,1019,376],{"class":375},[159,1021,727],{"class":172},[159,1023,1024],{"class":176}," usePortalCache ",[159,1026,181],{"class":180},[159,1028,1029],{"class":180}," ()",[159,1031,1032],{"class":172}," =>",[159,1034,184],{"class":180},[159,1036,1037],{"class":161,"line":187},[159,1038,1039],{"class":165},"  \u002F\u002F Application-level caching\n",[159,1041,1042,1045,1048,1051,1054,1057,1059,1062],{"class":161,"line":213},[159,1043,1044],{"class":172},"  const",[159,1046,1047],{"class":176}," cache",[159,1049,1050],{"class":180}," =",[159,1052,1053],{"class":382}," useNuxtApp",[159,1055,1056],{"class":190},"()",[159,1058,468],{"class":180},[159,1060,1061],{"class":176},"$cache",[159,1063,668],{"class":180},[159,1065,1066],{"class":161,"line":233},[159,1067,438],{"class":190},[159,1069,1070],{"class":161,"line":253},[159,1071,1072],{"class":165},"  \u002F\u002F Database query caching\n",[159,1074,1075,1077,1080,1082,1085,1088,1092,1094,1098,1100,1103,1105,1107,1109,1112,1115,1118,1121,1123],{"class":161,"line":273},[159,1076,1044],{"class":172},[159,1078,1079],{"class":176}," getCachedData",[159,1081,1050],{"class":180},[159,1083,1084],{"class":172}," async",[159,1086,1087],{"class":180}," (",[159,1089,1091],{"class":1090},"sHdIc","key",[159,1093,194],{"class":180},[159,1095,1097],{"class":1096},"sBMFI"," string",[159,1099,207],{"class":180},[159,1101,1102],{"class":382}," fetcher",[159,1104,194],{"class":180},[159,1106,1029],{"class":180},[159,1108,1032],{"class":172},[159,1110,1111],{"class":1096}," Promise",[159,1113,1114],{"class":180},"\u003C",[159,1116,1117],{"class":1096},"any",[159,1119,1120],{"class":180},">)",[159,1122,1032],{"class":172},[159,1124,184],{"class":180},[159,1126,1127,1130,1133,1135,1138,1140,1142,1145,1147,1149,1151],{"class":161,"line":293},[159,1128,1129],{"class":172},"    const",[159,1131,1132],{"class":176}," cached",[159,1134,1050],{"class":180},[159,1136,1137],{"class":375}," await",[159,1139,1047],{"class":176},[159,1141,468],{"class":180},[159,1143,1144],{"class":382},"get",[159,1146,386],{"class":190},[159,1148,1091],{"class":176},[159,1150,665],{"class":190},[159,1152,668],{"class":180},[159,1154,1155,1158,1160,1163,1166,1169,1171],{"class":161,"line":311},[159,1156,1157],{"class":375},"    if",[159,1159,1087],{"class":190},[159,1161,1162],{"class":176},"cached",[159,1164,1165],{"class":190},") ",[159,1167,1168],{"class":375},"return",[159,1170,1132],{"class":176},[159,1172,668],{"class":180},[159,1174,1175],{"class":161,"line":441},[159,1176,1177],{"class":190},"    \n",[159,1179,1180,1182,1185,1187,1189,1191,1193],{"class":161,"line":447},[159,1181,1129],{"class":172},[159,1183,1184],{"class":176}," data",[159,1186,1050],{"class":180},[159,1188,1137],{"class":375},[159,1190,1102],{"class":382},[159,1192,1056],{"class":190},[159,1194,668],{"class":180},[159,1196,1197,1200,1202,1204,1207,1209,1211,1213,1215,1217,1221,1223,1226],{"class":161,"line":457},[159,1198,1199],{"class":375},"    await",[159,1201,1047],{"class":176},[159,1203,468],{"class":180},[159,1205,1206],{"class":382},"set",[159,1208,386],{"class":190},[159,1210,1091],{"class":176},[159,1212,207],{"class":180},[159,1214,1184],{"class":176},[159,1216,207],{"class":180},[159,1218,1220],{"class":1219},"sbssI"," 300",[159,1222,665],{"class":190},[159,1224,1225],{"class":180},";",[159,1227,1228],{"class":165}," \u002F\u002F 5-minute cache\n",[159,1230,1231,1234,1236],{"class":161,"line":482},[159,1232,1233],{"class":375},"    return",[159,1235,1184],{"class":176},[159,1237,668],{"class":180},[159,1239,1240],{"class":161,"line":501},[159,1241,1242],{"class":180},"  };\n",[159,1244,1245],{"class":161,"line":506},[159,1246,438],{"class":190},[159,1248,1249,1252,1254,1256],{"class":161,"line":511},[159,1250,1251],{"class":375},"  return",[159,1253,540],{"class":180},[159,1255,1079],{"class":176},[159,1257,1258],{"class":180}," };\n",[159,1260,1261],{"class":161,"line":517},[159,1262,314],{"class":180},[24,1264,1265,1268,1269,468],{},[30,1266,1267],{},"Cross-Link to Caching:","\nFor detailed caching strategies that ensure sub-15ms response times, see our ",[889,1270,1272],{"href":1271},"\u002Fblog\u002Frijmwoordenboek-caching-optimization","Caching Optimization Guide",[19,1274,1276],{"id":1275},"real-world-results","Real-World Results",[99,1278,1280],{"id":1279},"project-case-study-e-commerce-portal","Project Case Study: E-commerce Portal",[24,1282,1283,1286,1287,1290],{},[30,1284,1285],{},"Client",": Online retailer with 10,000+ customers\n",[30,1288,1289],{},"Requirements",": Customer dashboard, order tracking, invoice management",[24,1292,1293],{},[30,1294,1295],{},"Our Solution:",[34,1297,1298,1303,1308,1313,1318],{},[37,1299,1300,1302],{},[30,1301,63],{},": 6 weeks (vs 6 months estimated)",[37,1304,1305,1307],{},[30,1306,75],{},": 150ms average page load",[37,1309,1310,1312],{},[30,1311,686],{},": 45ms average (using our PostgreSQL optimization)",[37,1314,1315,1317],{},[30,1316,692],{},": 25ms login\u002Flogout",[37,1319,1320,1322],{},[30,1321,69],{},": €15,000 (vs €50,000+ traditional approach)",[24,1324,1325],{},[30,1326,1327],{},"Technical Implementation:",[149,1329,1331],{"className":151,"code":1330,"language":153,"meta":154,"style":154},"\u002F\u002F Optimized order query using our database techniques\nconst getOrders = async (userId: string) => {\n  return await prisma.order.findMany({\n    where: { userId },\n    include: {\n      items: {\n        include: { product: true }\n      }\n    },\n    orderBy: { createdAt: 'desc' },\n    take: 20\n  });\n  \u002F\u002F Query time: 45ms (vs 500ms+ without optimization)\n};\n",[156,1332,1333,1338,1364,1387,1401,1410,1419,1437,1442,1446,1469,1479,1488,1493],{"__ignoreMap":154},[159,1334,1335],{"class":161,"line":162},[159,1336,1337],{"class":165},"\u002F\u002F Optimized order query using our database techniques\n",[159,1339,1340,1342,1345,1347,1349,1351,1354,1356,1358,1360,1362],{"class":161,"line":169},[159,1341,173],{"class":172},[159,1343,1344],{"class":176}," getOrders ",[159,1346,181],{"class":180},[159,1348,1084],{"class":172},[159,1350,1087],{"class":180},[159,1352,1353],{"class":1090},"userId",[159,1355,194],{"class":180},[159,1357,1097],{"class":1096},[159,1359,665],{"class":180},[159,1361,1032],{"class":172},[159,1363,184],{"class":180},[159,1365,1366,1368,1370,1373,1375,1378,1380,1383,1385],{"class":161,"line":187},[159,1367,1251],{"class":375},[159,1369,1137],{"class":375},[159,1371,1372],{"class":176}," prisma",[159,1374,468],{"class":180},[159,1376,1377],{"class":176},"order",[159,1379,468],{"class":180},[159,1381,1382],{"class":382},"findMany",[159,1384,386],{"class":190},[159,1386,389],{"class":180},[159,1388,1389,1392,1394,1396,1399],{"class":161,"line":213},[159,1390,1391],{"class":190},"    where",[159,1393,194],{"class":180},[159,1395,540],{"class":180},[159,1397,1398],{"class":176}," userId",[159,1400,795],{"class":180},[159,1402,1403,1406,1408],{"class":161,"line":233},[159,1404,1405],{"class":190},"    include",[159,1407,194],{"class":180},[159,1409,184],{"class":180},[159,1411,1412,1415,1417],{"class":161,"line":253},[159,1413,1414],{"class":190},"      items",[159,1416,194],{"class":180},[159,1418,184],{"class":180},[159,1420,1421,1424,1426,1428,1431,1433,1435],{"class":161,"line":273},[159,1422,1423],{"class":190},"        include",[159,1425,194],{"class":180},[159,1427,540],{"class":180},[159,1429,1430],{"class":190}," product",[159,1432,194],{"class":180},[159,1434,554],{"class":422},[159,1436,585],{"class":180},[159,1438,1439],{"class":161,"line":293},[159,1440,1441],{"class":180},"      }\n",[159,1443,1444],{"class":161,"line":311},[159,1445,591],{"class":180},[159,1447,1448,1451,1453,1455,1458,1460,1462,1465,1467],{"class":161,"line":441},[159,1449,1450],{"class":190},"    orderBy",[159,1452,194],{"class":180},[159,1454,540],{"class":180},[159,1456,1457],{"class":190}," createdAt",[159,1459,194],{"class":180},[159,1461,197],{"class":180},[159,1463,1464],{"class":200},"desc",[159,1466,204],{"class":180},[159,1468,795],{"class":180},[159,1470,1471,1474,1476],{"class":161,"line":447},[159,1472,1473],{"class":190},"    take",[159,1475,194],{"class":180},[159,1477,1478],{"class":1219}," 20\n",[159,1480,1481,1484,1486],{"class":161,"line":457},[159,1482,1483],{"class":180},"  }",[159,1485,665],{"class":190},[159,1487,668],{"class":180},[159,1489,1490],{"class":161,"line":482},[159,1491,1492],{"class":165},"  \u002F\u002F Query time: 45ms (vs 500ms+ without optimization)\n",[159,1494,1495],{"class":161,"line":501},[159,1496,314],{"class":180},[19,1498,1500],{"id":1499},"key-success-factors","Key Success Factors",[99,1502,1504],{"id":1503},"_1-standardized-architecture-patterns","1. Standardized Architecture Patterns",[34,1506,1507,1513,1518],{},[37,1508,1509,1512],{},[30,1510,1511],{},"Consistent Structure",": Every portal follows the same architecture",[37,1514,1515,1517],{},[30,1516,129],{},": UI components work across all portals",[37,1519,1520,1523],{},[30,1521,1522],{},"Proven Patterns",": Battle-tested solutions for common problems",[99,1525,1527],{"id":1526},"_2-performance-first-development","2. Performance-First Development",[34,1529,1530,1534,1540],{},[37,1531,1532,136],{},[30,1533,135],{},[37,1535,1536,1539],{},[30,1537,1538],{},"Caching Strategy",": Multi-layer caching for optimal performance",[37,1541,1542,1545],{},[30,1543,1544],{},"Code Splitting",": Optimized bundle sizes for fast loading",[99,1547,1549],{"id":1548},"_3-modern-development-practices","3. Modern Development Practices",[34,1551,1552,1558,1564],{},[37,1553,1554,1557],{},[30,1555,1556],{},"TypeScript",": Type safety prevents runtime errors",[37,1559,1560,1563],{},[30,1561,1562],{},"Automated Testing",": Comprehensive test coverage",[37,1565,1566,1569],{},[30,1567,1568],{},"CI\u002FCD Pipeline",": Automated deployment and testing",[99,1571,1573],{"id":1572},"_4-client-communication","4. Client Communication",[34,1575,1576,1582,1588],{},[37,1577,1578,1581],{},[30,1579,1580],{},"Clear Requirements",": Structured approach to gathering requirements",[37,1583,1584,1587],{},[30,1585,1586],{},"Regular Updates",": Weekly progress reports and demos",[37,1589,1590,1593],{},[30,1591,1592],{},"Flexible Scope",": Ability to adjust features during development",[19,1595,1597],{"id":1596},"implementation-checklist","Implementation Checklist",[24,1599,1600],{},"If you're building customer portals and want to achieve similar results:",[34,1602,1605,1618,1627,1636,1645,1654,1663],{"className":1603},[1604],"contains-task-list",[37,1606,1609,1613,1614,1617],{"className":1607},[1608],"task-list-item",[1610,1611],"input",{"disabled":941,"type":1612},"checkbox"," ",[30,1615,1616],{},"Choose a unified tech stack",": Reduce complexity and maintenance",[37,1619,1621,1613,1623,1626],{"className":1620},[1608],[1610,1622],{"disabled":941,"type":1612},[30,1624,1625],{},"Implement performance optimization",": Database tuning and caching",[37,1628,1630,1613,1632,1635],{"className":1629},[1608],[1610,1631],{"disabled":941,"type":1612},[30,1633,1634],{},"Use standardized authentication",": Don't rebuild auth systems",[37,1637,1639,1613,1641,1644],{"className":1638},[1608],[1610,1640],{"disabled":941,"type":1612},[30,1642,1643],{},"Apply proven architecture patterns",": Leverage existing solutions",[37,1646,1648,1613,1650,1653],{"className":1647},[1608],[1610,1649],{"disabled":941,"type":1612},[30,1651,1652],{},"Optimize database queries",": Use our PostgreSQL techniques",[37,1655,1657,1613,1659,1662],{"className":1656},[1608],[1610,1658],{"disabled":941,"type":1612},[30,1660,1661],{},"Implement comprehensive caching",": Multi-layer caching strategy",[37,1664,1666,1613,1668,1671],{"className":1665},[1608],[1610,1667],{"disabled":941,"type":1612},[30,1669,1670],{},"Plan for scalability",": Design for growth from day one",[19,1673,1675],{"id":1674},"cross-linked-resources","Cross-Linked Resources",[24,1677,1678],{},"Our customer portal development leverages expertise from multiple areas:",[34,1680,1681,1689,1697,1705,1714],{},[37,1682,1683,1688],{},[30,1684,1685],{},[889,1686,1687],{"href":997},"PostgreSQL Performance Tuning",": Database optimization techniques",[37,1690,1691,1696],{},[30,1692,1693],{},[889,1694,1695],{"href":1271},"Caching Optimization",": Sub-15ms response times",[37,1698,1699,1704],{},[30,1700,1701],{},[889,1702,1703],{"href":891},"Authentication Strategies",": Secure, fast authentication",[37,1706,1707,1713],{},[30,1708,1709],{},[889,1710,1712],{"href":1711},"\u002Fblog\u002Fsaas-architecture-patterns","SaaS Architecture Patterns",": Scalable application design",[37,1715,1716,1722],{},[30,1717,1718],{},[889,1719,1721],{"href":1720},"\u002Fblog\u002Ftypescript-best-practices","TypeScript Best Practices",": Type-safe development",[19,1724,1726],{"id":1725},"summary","Summary",[24,1728,1729],{},"Customer portal development doesn't have to take 6 months or cost €50,000+. By combining proven architecture patterns with performance optimization techniques, we've reduced development time by 75% while improving performance by 90%.",[24,1731,1732],{},"The key is leveraging existing expertise and building reusable patterns rather than starting from scratch each time.",[24,1734,1735],{},"If this article helped you understand streamlined portal development, we can help you build your own high-performance customer portal. At Ludulicious, we specialize in:",[34,1737,1738,1744,1750,1756],{},[37,1739,1740,1743],{},[30,1741,1742],{},"Customer Portal Development",": Fast, scalable portal solutions",[37,1745,1746,1749],{},[30,1747,1748],{},"Database Performance Optimization",": Sub-100ms query performance",[37,1751,1752,1755],{},[30,1753,1754],{},"Authentication Systems",": Secure, modern authentication",[37,1757,1758,1761],{},[30,1759,1760],{},"Custom Development",": Tailored solutions for your specific needs",[24,1763,1764],{},[30,1765,1766],{},"Ready to build your customer portal in 6 weeks instead of 6 months?",[24,1768,1769,1773],{},[889,1770,1772],{"href":1771},"\u002Fcontact","Contact us"," for a free consultation, or check out our other development guides:",[34,1775,1776,1781,1787,1793,1798],{},[37,1777,1778],{},[889,1779,1780],{"href":997},"PostgreSQL Performance Tuning: Strategic Lessons from Production",[37,1782,1783],{},[889,1784,1786],{"href":1785},"\u002Fblog\u002Fduikersgids-spatial-search-optimization","Duikersgids: How I Made Spatial Search 55x Faster",[37,1788,1789],{},[889,1790,1792],{"href":1791},"\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization","Rijmwoordenboek: Solving the 3-Second Phonetic Search Problem",[37,1794,1795],{},[889,1796,1797],{"href":1711},"SaaS Architecture Patterns: Building Scalable Applications",[37,1799,1800],{},[889,1801,1802],{"href":891},"Authentication Strategies: Secure, Fast User Management",[1804,1805],"hr",{},[24,1807,1808],{},[1809,1810,1811],"em",{},"This customer portal development guide is based on real production experience building portals for clients. All performance numbers and timelines are from actual projects.",[1813,1814,1815],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":154,"searchDepth":169,"depth":169,"links":1817},[1818,1819,1822,1826,1831,1834,1840,1841,1842],{"id":21,"depth":169,"text":22},{"id":96,"depth":169,"text":97,"children":1820},[1821],{"id":101,"depth":187,"text":102},{"id":139,"depth":169,"text":140,"children":1823},[1824,1825],{"id":143,"depth":187,"text":144},{"id":356,"depth":187,"text":357},{"id":702,"depth":169,"text":703,"children":1827},[1828,1829,1830],{"id":706,"depth":187,"text":707},{"id":895,"depth":187,"text":896},{"id":1001,"depth":187,"text":1002},{"id":1275,"depth":169,"text":1276,"children":1832},[1833],{"id":1279,"depth":187,"text":1280},{"id":1499,"depth":169,"text":1500,"children":1835},[1836,1837,1838,1839],{"id":1503,"depth":187,"text":1504},{"id":1526,"depth":187,"text":1527},{"id":1548,"depth":187,"text":1549},{"id":1572,"depth":187,"text":1573},{"id":1596,"depth":169,"text":1597},{"id":1674,"depth":169,"text":1675},{"id":1725,"depth":169,"text":1726},[1844,1845],"Web Development","Product Development","2025-10-22","Learn how we streamlined customer portal development, reducing build time from 6 months to 6 weeks using our proven architecture patterns, authentication strategies, and performance optimization techniques.","md",{"src":1850},"https:\u002F\u002Fpicsum.photos\u002Fid\u002F3\u002F640\u002F360",{},"\u002Fblog\u002Fcustomer-portal",{"title":5,"description":1847},"blog\u002F1.customer-portal",[14,1856,692,1857,1556,1858,1859],"Nuxt.js","SaaS","Performance Optimization","Architecture Patterns","hRePqv2iBG9WRUVIQNcqfzC8frrDYKhSXUvZ_QQX2Qs",[1862,1863],null,{"title":1864,"path":1865,"stem":1866,"description":1867,"children":-1},"PostgreSQL Configuration: The Settings That Matter","\u002Fblog\u002Fpostgresql-configuration-optimization","blog\u002F10.postgresql-configuration-optimization","Learn the essential PostgreSQL configuration settings that impact performance, from memory allocation to connection pooling. Based on real production experience optimizing databases for high-traffic applications.",[]]