diff --git a/VERSIONS_HOTLINE/197905 b/VERSIONS_HOTLINE/197905
new file mode 100644
index 0000000000000000000000000000000000000000..ddeaf48c096ca09d6a43a15bf0560306349e633a
--- /dev/null
+++ b/VERSIONS_HOTLINE/197905
@@ -0,0 +1 @@
+ - correctif #197905 : Performance : ajout d'un cache sur l'affichage des documents pour améliorer les performances d'affichage du portail.
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/View/Wrapper/Record.php b/library/templates/Intonation/Library/View/Wrapper/Record.php
index d2fd4eb45e3e594afd6197ceca2a6915849e4638..8dc71a4ac16b55844992551137a2a271db2cff97 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Record.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Record.php
@@ -25,6 +25,7 @@ class Intonation_Library_View_Wrapper_Record
 
   use Trait_DecorateLabelCallback;
 
+  protected static $_urls_by_model_cache = [];
   protected
     $_picture_cache,
     $_selectable_action = false,
@@ -34,6 +35,19 @@ class Intonation_Library_View_Wrapper_Record
     $_badges,
     $_allow_XSL = false;
 
+  /** @testing */
+  public static function resetUrlsByModelCache(): void
+  {
+    static::$_urls_by_model_cache = [];
+  }
+
+
+  /** @testing */
+  public static function urlsByModelCache(): array
+  {
+    return static::$_urls_by_model_cache;
+  }
+
 
   public function getMainTitle() : string {
     return $this->_main_title = $this->_main_title
@@ -82,24 +96,29 @@ class Intonation_Library_View_Wrapper_Record
   }
 
 
-  protected function _recordUrl() : string {
+  protected function _recordUrl(): string
+  {
     $widget_context = $this->getWidgetContext();
 
-    if (!$this->_model
-        || !$widget_context instanceof Intonation_Library_Widget_Carousel_Record_View)
+    if ( ! $this->_model
+        || ! $widget_context instanceof Intonation_Library_Widget_Carousel_Record_View)
       return $this->_defaultRecordUrl();
 
-    if (!$widget_context->isViewLinkSpecific())
+    if ( ! $widget_context->isViewLinkSpecific())
       return $this->_viewnoticeRecordUrl();
 
+    $id = $this->_model->getId();
+    if ($url = (static::$_urls_by_model_cache[$id] ?? ''))
+      return $url;
+
     if ($website = $this->_model->getSite())
-      return $website->getUrl();
+      return static::$_urls_by_model_cache[$id] = $website->getUrl();
 
     $directurl = Class_Album_DirectUrl::newForRecord($this->_model);
     if ($url = $directurl->url())
-      return $url;
+      return static::$_urls_by_model_cache[$id] = $url;
 
-    return $this->_defaultRecordUrl();
+    return static::$_urls_by_model_cache[$id] = $this->_defaultRecordUrl();
   }
 
 
diff --git a/tests/TearDown.php b/tests/TearDown.php
index 9a63b7fc788e5764c0fdfcf1b4f815bab553699d..3004f90480ae41e8a1df886336e5dc91b04cdd42 100644
--- a/tests/TearDown.php
+++ b/tests/TearDown.php
@@ -72,6 +72,7 @@ class TearDown {
     Class_StatsNotices::setTimeSource(null);
 
     Class_PanierNotice_RecordKeys::reset();
+    Intonation_Library_View_Wrapper_Record::resetUrlsByModelCache();
 
     Class_Profil::reset();
 
diff --git a/tests/application/modules/AbstractControllerTestCase.php b/tests/application/modules/AbstractControllerTestCase.php
index fb0feafb88f483beec4bcd0434e8af394bc1d615..f2fce49686fabda0bc5dcf39010f8121a95b5a5f 100644
--- a/tests/application/modules/AbstractControllerTestCase.php
+++ b/tests/application/modules/AbstractControllerTestCase.php
@@ -168,6 +168,7 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe
     $this->_login();
     Class_InspectorGadget::setInstance(null);
     Class_User_RecordCarts::resetCache();
+    Intonation_Library_View_Wrapper_Record::resetUrlsByModelCache();
 
     $this->_querySetUp();
   }
diff --git a/tests/scenarios/Templates/TemplatesWidgetRecordsTest.php b/tests/scenarios/Templates/TemplatesWidgetRecordsTest.php
index 093a7583550379071ef2ffd485c5fe536379a24c..f1ef472d512134ff0b3ad98260c23e6c686af10b 100644
--- a/tests/scenarios/Templates/TemplatesWidgetRecordsTest.php
+++ b/tests/scenarios/Templates/TemplatesWidgetRecordsTest.php
@@ -21,10 +21,11 @@
 
 require_once 'TemplatesTest.php';
 
+class TemplatesWidgetRecordsAdminTest extends TemplatesIntonationTestCase
+{
 
-class TemplatesWidgetRecordsAdminTest extends TemplatesIntonationTestCase {
-
-  public function setUp() {
+  public function setUp()
+  {
     parent::setUp();
 
     $this->dispatch('/admin/widget/edit-widget/id/17/id_profil/72');
@@ -32,13 +33,15 @@ class TemplatesWidgetRecordsAdminTest extends TemplatesIntonationTestCase {
 
 
   /** @test */
-  public function pageShouldContainsOrderSelect() {
+  public function pageShouldContainsOrderSelect()
+  {
     $this->assertXpath('//select[@name="order"]');
   }
 
 
   /** @test */
-  public function pageShouldHaveExpertViewLinkModeViewSpecificSelected() {
+  public function pageShouldHaveExpertViewLinkModeViewSpecificSelected()
+  {
     $this->assertXpath('//tr[@data-level="expert"]//select[@name="view_link_mode"]//option[@selected][@value="view_specific"]');
   }
 }
@@ -46,9 +49,11 @@ class TemplatesWidgetRecordsAdminTest extends TemplatesIntonationTestCase {
 
 
 
-class TemplatesWidgetRecordsFrontViewLinkModeTest extends TemplatesIntonationTestCase {
+class TemplatesWidgetRecordsFrontViewLinkModeTest extends TemplatesIntonationTestCase
+{
 
-  public function setUp() {
+  public function setUp()
+  {
     parent::setUp();
 
     $article = $this->fixture(Class_Article::class,
@@ -165,7 +170,8 @@ class TemplatesWidgetRecordsFrontViewLinkModeTest extends TemplatesIntonationTes
   }
 
 
-  public function specificLinks() : array {
+  public function specificLinks(): array
+  {
     return [
             ['/cms/articleview/id/12'],
             ['/rss/main/id_flux/12'],
@@ -182,7 +188,8 @@ class TemplatesWidgetRecordsFrontViewLinkModeTest extends TemplatesIntonationTes
 
 
   /** @test */
-  public function screenReaderShouldDisplayDescription() {
+  public function screenReaderShouldDisplayDescription()
+  {
     Class_Systeme_Widget_Widget::updateInProfile(72, 17,
                                                  ['size' => 100,
                                                   'view_link_mode' => 'view_specific']);
@@ -191,25 +198,52 @@ class TemplatesWidgetRecordsFrontViewLinkModeTest extends TemplatesIntonationTes
   }
 
 
-  /** @test @dataProvider specificLinks */
-  public function withViewSpecifiModePageShouldContainsSpecificLink(string $link) {
+  /**
+   * @test
+   * @dataProvider specificLinks
+   */
+  public function withViewSpecifiModePageShouldContainsSpecificLink(string $link)
+  {
     Class_Systeme_Widget_Widget::updateInProfile(72, 17,
                                                  ['size' => 100,
                                                   'view_link_mode' => 'view_specific']);
     $this->dispatch('/opac/index/index/id_profil/72');
-    $this->assertXPath('//div[@id="boite_17"]//a[contains(@href, "' . $link . '")]',
-                       $this->_response->getBody());
+    $this->assertXPath('//div[@id="boite_17"]//a[contains(@href, "' . $link . '")]');
+  }
+
+
+  /** @test */
+  public function urlCacheShouldHaveBeenInitialised()
+  {
+    Class_Systeme_Widget_Widget::updateInProfile(72, 17, ['size' => 100,
+                                                          'view_link_mode' => 'view_specific']);
+    $this->dispatch('/opac/index/index/id_profil/72');
+
+    $urls = Intonation_Library_View_Wrapper_Record::urlsByModelCache();
+
+    $this->assertEquals('https://admin-pad.philharmoniedeparis.fr/logon/22?backUrl=https%3A%2F%2Fpad.philharmoniedeparis.org', $urls[9]);
+    $this->assertEquals('https://www.jamendo.com/fr/list/a88399', $urls[8]);
+    $this->assertEquals('http://www.bibliovox.com/?docid=eefc99382edb', $urls[7]);
+    $this->assertEquals('https://very.cool', $urls[5]);
+    $this->assertEquals('https://very.good.io', $urls[3]);
+
+    $this->assertEquals(10, count($urls));
   }
 
 
-  public function recordLinks() : array {
+  public function recordLinks(): array
+  {
     return array_map(fn($id) => ['/recherche/viewnotice/id/' . $id],
                      range(1, 10));
   }
 
 
-  /** @test @dataProvider recordLinks */
-  public function withViewRecordModePageShouldContainsRecordLink(string $link) {
+  /**
+   * @test
+   * @dataProvider recordLinks
+   */
+  public function withViewRecordModePageShouldContainsRecordLink(string $link)
+  {
     Class_Systeme_Widget_Widget::updateInProfile(72, 17,
                                                  ['size' => 100,
                                                   'view_link_mode' => 'view_record']);
@@ -221,12 +255,14 @@ class TemplatesWidgetRecordsFrontViewLinkModeTest extends TemplatesIntonationTes
 
 
 
-class TemplatesWidgetRecordsFrontTest extends TemplatesIntonationTestCase {
+class TemplatesWidgetRecordsFrontTest extends TemplatesIntonationTestCase
+{
 
-  public function setUp() {
+  public function setUp()
+  {
     parent::setUp();
 
-    foreach(range(1, 6) as $step)
+    foreach (range(1, 6) as $step)
       $this->fixture(Class_Notice::class,
                      ['id' => $step,
                       'titre_principal' => 'Inspecteur Lapou - ' . $step,
@@ -239,14 +275,16 @@ class TemplatesWidgetRecordsFrontTest extends TemplatesIntonationTestCase {
 
 
   /** @test */
-  public function pageShouldContainsRahan() {
+  public function pageShouldContainsRahan()
+  {
     $this->dispatch('/opac/index/index/id_profil/72');
     $this->assertXPathContentContains('//div', 'Rahan');
   }
 
 
   /** @test */
-  public function withCarouselPageShouldContainsRahan() {
+  public function withCarouselPageShouldContainsRahan()
+  {
     $widget = ((new Class_Systeme_Widget_Widget)
                ->setId(17)
                ->setProfileId(72)
@@ -261,7 +299,8 @@ class TemplatesWidgetRecordsFrontTest extends TemplatesIntonationTestCase {
 
 
   /** @test */
-  public function withWallCardPageShouldContainsManyInspecteurLapu() {
+  public function withWallCardPageShouldContainsManyInspecteurLapu()
+  {
     $widget = ((new Class_Systeme_Widget_Widget)
                ->setId(17)
                ->setProfileId(72)
@@ -272,7 +311,7 @@ class TemplatesWidgetRecordsFrontTest extends TemplatesIntonationTestCase {
       ->updateProfile();
 
     $this->dispatch('/opac/index/index/id_profil/72');
-    foreach(range(1, 6) as $step)
+    foreach (range(1, 6) as $step)
       $this->assertXPathContentContains('//div', 'Inspecteur Lapou - ' . $step);
   }
 }
@@ -280,9 +319,11 @@ class TemplatesWidgetRecordsFrontTest extends TemplatesIntonationTestCase {
 
 
 
-class TemplatesWidgetRecordsFrontMultipleCarouselTest extends TemplatesIntonationTestCase {
+class TemplatesWidgetRecordsFrontMultipleCarouselTest extends TemplatesIntonationTestCase
+{
 
-  public function setUp() {
+  public function setUp()
+  {
     parent::setUp();
 
     $widget = ((new Class_Systeme_Widget_Widget)
@@ -307,31 +348,36 @@ class TemplatesWidgetRecordsFrontMultipleCarouselTest extends TemplatesIntonatio
 
 
   /** @test */
-  public function pageShouldContainsRahan() {
+  public function pageShouldContainsRahan()
+  {
     $this->assertXPathContentContains('//div', 'Rahan');
   }
 
 
   /** @test */
-  public function pageShouldContainsWidgetRssLink() {
+  public function pageShouldContainsWidgetRssLink()
+  {
     $this->assertXPath('//a[contains(@title, "Flux RSS de la boite")][contains(@href, "/rss/kiosque?id_module=17")]');
   }
 
 
   /** @test */
-  public function pageShouldContainsEmbededCodeWithAnchorTargetBlank() {
+  public function pageShouldContainsEmbededCodeWithAnchorTargetBlank()
+  {
     $this->assertXPathContentContains('//pre', '/widget/render/widget_id/17/profile_id/72/anchor_target/_blank');
   }
 
 
   /** @test */
-  public function pageShouldContainsAllRecordsLink() {
+  public function pageShouldContainsAllRecordsLink()
+  {
     $this->assertXPath('//a[contains(@href, "/recherche/simple")][contains(@title, "Voir tous les documents de la liste")]');
   }
 
 
   /** @test */
-  public function pageShouldContainsAllRecordsLinkHref() {
+  public function pageShouldContainsAllRecordsLinkHref()
+  {
     $this->assertXPath('//a[contains(@href, "/recherche/simple/tri/alpha_titre+ASC/titre/Boite+de+notices")][contains(@title, "Voir tous les documents")]');
   }
 }
@@ -339,9 +385,11 @@ class TemplatesWidgetRecordsFrontMultipleCarouselTest extends TemplatesIntonatio
 
 
 
-class TemplatesWidgetRecordsRenderWidgetTest extends TemplatesIntonationTestCase {
+class TemplatesWidgetRecordsRenderWidgetTest extends TemplatesIntonationTestCase
+{
 
-  public function setUp() {
+  public function setUp()
+  {
     parent::setUp();
 
     ZendAfi_Auth::getInstance()->clearIdentity();
@@ -350,13 +398,15 @@ class TemplatesWidgetRecordsRenderWidgetTest extends TemplatesIntonationTestCase
 
 
   /** @test */
-  public function pageShouldContainsBoiteDeNotices() {
+  public function pageShouldContainsBoiteDeNotices()
+  {
     $this->assertXPathContentContains('//div', 'Boite de notices');
   }
 
 
   /** @test */
-  public function pageShouldBeHtml5Valid() {
+  public function pageShouldBeHtml5Valid()
+  {
     $this->assertHTML5();
   }
 }
@@ -364,12 +414,14 @@ class TemplatesWidgetRecordsRenderWidgetTest extends TemplatesIntonationTestCase
 
 
 
-class TemplatesWidgetRecordsWithPanierTest extends TemplatesIntonationTestCase {
+class TemplatesWidgetRecordsWithPanierTest extends TemplatesIntonationTestCase
+{
 
   protected bool $_storm_mock_zend_adapter = true;
   protected array $_storm_scopes = ['Intonation_Library_Widget_Carousel_Record_View'];
 
-  public function setUp() {
+  public function setUp()
+  {
     parent::setUp();
 
     $widget = ((new Class_Systeme_Widget_Widget)
@@ -399,7 +451,8 @@ class TemplatesWidgetRecordsWithPanierTest extends TemplatesIntonationTestCase {
 
 
   /** @test */
-  public function sqlShouldContainsSearchByAlphaKeyRAHAN() {
+  public function sqlShouldContainsSearchByAlphaKeyRAHAN()
+  {
     $this->assertSql("SELECT `notices`.* FROM `notices` WHERE (`notices`.`clef_alpha` IN ('RAHAN')) ORDER BY `notices`.`url_image` = 'no' ASC, `notices`.`url_image` = '' ASC, `notices`.`alpha_titre` ASC LIMIT 9");
   }
 }