diff --git a/src/routes/[lang]/onlineboard/flight/[params]/page.test.tsx b/src/routes/[lang]/onlineboard/flight/[params]/page.test.tsx
new file mode 100644
index 00000000..de0ddec5
--- /dev/null
+++ b/src/routes/[lang]/onlineboard/flight/[params]/page.test.tsx
@@ -0,0 +1,110 @@
+/**
+ * Tests for the flight number Online-Board route page.
+ *
+ * Verifies that out-of-window dates redirect to /{locale}/onlineboard (TZ §4.1.2),
+ * in-window dates render normally, and malformed URLs still produce 404.
+ *
+ * @vitest-environment jsdom
+ */
+
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { render, screen } from "@testing-library/react";
+import React from "react";
+
+// Freeze clock: today = 2026-05-15. Board window: 2026-05-14 … 2026-05-29.
+beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date(2026, 4, 15, 12));
+});
+afterEach(() => {
+ vi.useRealTimers();
+});
+
+// --- mocks ---
+
+vi.mock("@/i18n/provider.js", () => ({
+ useTranslation: () => ({ t: (k: string) => k, i18n: { language: "ru" } }),
+}));
+
+vi.mock("@/env/index.js", () => ({
+ getEnv: () => ({ PROD_ORIGIN: "https://aeroflot.ru" }),
+}));
+
+vi.mock("@/ui/seo/SeoHead.js", () => ({
+ SeoHead: () =>
,
+}));
+
+vi.mock("@/ui/errors/ErrorPage.js", () => ({
+ ErrorPage: ({ code }: { code: string }) => ,
+}));
+
+vi.mock("@/ui/flights/FlightListSkeleton.js", () => ({
+ FlightListSkeleton: () => ,
+}));
+
+vi.mock(
+ "@/features/online-board/components/OnlineBoardSearchPage.js",
+ () => ({ OnlineBoardSearchPage: () => }),
+);
+
+vi.mock("@/features/online-board/seo.js", () => ({
+ buildFlightSearchSeo: () => ({}),
+}));
+
+// Track Navigate calls
+const mockNavigate = vi.fn();
+vi.mock("@modern-js/runtime/router", () => ({
+ useParams: vi.fn(),
+ Navigate: ({ to }: { to: string }) => {
+ mockNavigate(to);
+ return ;
+ },
+}));
+
+import { useParams } from "@modern-js/runtime/router";
+import FlightSearchPage from "./page.js";
+
+function renderWithParams(params: string, lang = "ru-ru") {
+ vi.mocked(useParams).mockReturnValue({ params, lang });
+ return render();
+}
+
+describe("FlightSearchPage — date-window guard", () => {
+ beforeEach(() => {
+ mockNavigate.mockReset();
+ });
+
+ it("renders normally for an in-window date (today)", () => {
+ renderWithParams("SU1-20260515");
+ expect(screen.getByTestId("seo-head")).toBeTruthy();
+ expect(screen.queryByTestId("navigate")).toBeNull();
+ });
+
+ it("renders normally for the boundary date (today - 1)", () => {
+ renderWithParams("SU1-20260514");
+ expect(screen.queryByTestId("navigate")).toBeNull();
+ });
+
+ it("redirects to /{locale}/onlineboard when date is too late (today + 15)", () => {
+ renderWithParams("SU1-20260530");
+ expect(screen.getByTestId("navigate")).toBeTruthy();
+ expect(mockNavigate).toHaveBeenCalledWith("/ru-ru/onlineboard");
+ });
+
+ it("redirects to /{locale}/onlineboard when date is too early (today - 2)", () => {
+ renderWithParams("SU1-20260513");
+ expect(screen.getByTestId("navigate")).toBeTruthy();
+ expect(mockNavigate).toHaveBeenCalledWith("/ru-ru/onlineboard");
+ });
+
+ it("uses the route locale in the redirect path", () => {
+ renderWithParams("SU1-20260530", "en-us");
+ expect(mockNavigate).toHaveBeenCalledWith("/en-us/onlineboard");
+ });
+
+ it("shows 404 for a malformed URL (no date)", () => {
+ renderWithParams("SU1");
+ expect(screen.getByTestId("error-404")).toBeTruthy();
+ expect(screen.queryByTestId("navigate")).toBeNull();
+ });
+});
diff --git a/src/routes/[lang]/onlineboard/flight/[params]/page.tsx b/src/routes/[lang]/onlineboard/flight/[params]/page.tsx
index 762fcfcd..2126db5d 100644
--- a/src/routes/[lang]/onlineboard/flight/[params]/page.tsx
+++ b/src/routes/[lang]/onlineboard/flight/[params]/page.tsx
@@ -6,7 +6,7 @@
*/
import { lazy, Suspense } from "react";
-import { useParams } from "@modern-js/runtime/router";
+import { useParams, Navigate } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import { parseFlightUrlParams } from "@/features/online-board/url.js";
import { buildFlightSearchSeo } from "@/features/online-board/seo.js";
@@ -14,6 +14,7 @@ import { SeoHead } from "@/ui/seo/SeoHead.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { ErrorPage } from "@/ui/errors/ErrorPage.js";
import { getEnv } from "@/env/index.js";
+import { boardDateRedirect } from "../../_guards.js";
const OnlineBoardSearchPage = lazy(() =>
import("@/features/online-board/components/OnlineBoardSearchPage.js").then(
@@ -30,6 +31,9 @@ export default function FlightSearchPage(): JSX.Element {
if (!parsed) return ;
+ const redirect = boardDateRedirect(locale, parsed.date);
+ if (redirect) return ;
+
const canonicalOrigin = getEnv().PROD_ORIGIN;
const searchParams = parsed.suffix
? {