'callbacks.postWatcher']) { $this->ssrHandler = new SSRHandler($interactiveNamespace, $rootElementId, $rootElementAttributes); } /** * Handle the root route and render the topics page. * * @param int|string $pageNumber list page, 1 unless a `/page/{n}` route matched * * @return string */ public function index(Request $request, $pageNumber = 1) { $page = max(1, (int) $pageNumber); // Before anything is rendered, so a plugin that answers some form of // this URL with a redirect — an older address of one of its archives — // can still send one. Hooks::doAction('bit_connect_before_topic_list', $request); // Prepare data for the topics view $topicsView = new TopicsView(); $topicsView->prepareData($page); // Get state data to pass to the view $stateData = $topicsView->getState(); // The view data has to be handed to generateView() as well: it builds its // own SSRView through the ViewManager, so anything left on $topicsView is // discarded. Without it the render has no content to emit for crawlers. $viewData = $topicsView->getViewData(); // A page past the end is not a thin page, it is no page — answering 200 // would turn every out-of-range number into an indexable empty listing. // A members-only portal's guest was given no topics to count, and gets // the sign-in prompt instead. if ($page > 1 && empty($viewData['topics']) && !TopicsView::isClosed()) { return (new NotFoundController())->index($request); } SeoMeta::forTopics($viewData['topics'] ?? [], $topicsView->getCurrentPage()); // Generate the view using the SSR system $route = $page > 1 ? '/page/' . $page : '/'; return $this->ssrHandler->generateView($route, $stateData, [], $viewData); } }