From 8aa147c25a6aeb0edd7311af9465d36fc965bcdf Mon Sep 17 00:00:00 2001 From: StanisLove Date: Tue, 8 Sep 2026 23:34:56 +0300 Subject: [PATCH] fix: keep empty preview visible before hydration --- AGENTS.md | 2 +- README.md | 8 ++++---- package.json | 3 ++- scripts/verify-no-js-preview.mjs | 27 +++++++++++++++++++++++++++ src/app/page.tsx | 13 ++----------- 5 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 scripts/verify-no-js-preview.mjs diff --git a/AGENTS.md b/AGENTS.md index 384a28d..5096234 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ - `src/app/page.tsx` — стартовая заставка (Fluw splash). Это placeholder: при старте реального продукта заменяй его композицией страниц, а не оставляй заставку. - Раскладывай новый UI по FSD: страницы-композиции → `src/widgets/*`, пользовательские сценарии → `src/features/*`, бизнес-данные → `src/entities/*`, примитивы — `src/shared/*`. Не складывай всё в один файл. -- Базовые UI-примитивы shadcn — в `src/shared/ui/*` (vendored, не переписывай). Доступен `framer-motion` (анимации заставки). +- Базовые UI-примитивы shadcn — в `src/shared/ui/*` (vendored, не переписывай). Доступен `framer-motion`, но первый содержательный кадр обязан оставаться видимым без hydration: не оборачивай весь экран в `initial={{ opacity: 0 }}`. - Проверка после правок: `pnpm lint` и `pnpm build`. ## Design System diff --git a/README.md b/README.md index 4a8484e..a0d9798 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ ## Анимации: framer-motion -Для анимаций используется `framer-motion`. Это мощная библиотека для создания плавных и производительных анимаций в React-компонентах. +Для анимаций доступен `framer-motion`. Содержательный интерфейс должен оставаться видимым до hydration, поэтому не задавайте `opacity: 0` как исходное состояние для всего экрана или другого критичного контейнера. Пример использования: @@ -81,9 +81,9 @@ import { motion } from 'framer-motion'; function AnimatedComponent() { return ( Анимированный контент diff --git a/package.json b/package.json index f6f9a65..c23153f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "scripts": { "dev": "next dev", "type-check": "tsc --noEmit", - "build": "next build", + "build": "next build && pnpm verify:no-js-preview", + "verify:no-js-preview": "node scripts/verify-no-js-preview.mjs", "start": "next start", "lint": "oxlint --threads=2 --deny-warnings", "lint:fix": "oxlint --threads=2 --type-aware --fix" diff --git a/scripts/verify-no-js-preview.mjs b/scripts/verify-no-js-preview.mjs new file mode 100644 index 0000000..35d40d7 --- /dev/null +++ b/scripts/verify-no-js-preview.mjs @@ -0,0 +1,27 @@ +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; + +const outputPath = resolve(".next/server/app/index.html"); +const html = await readFile(outputPath, "utf8"); + +// The placeholder is the first frame for a newly-created project. It must stay +// visible when a client chunk is delayed or rejected; later product pages are +// free to replace it and choose their own bounded animation policy. +if (html.includes("Fluw template")) { + const main = html.match(//i)?.[0]; + if (!main) { + throw new Error("Fluw placeholder is missing its main landmark"); + } + + if ( + /style="[^"]*(?:opacity\s*:\s*0(?:[;"]|\s)|visibility\s*:\s*hidden|display\s*:\s*none)/i.test( + main, + ) + ) { + throw new Error( + "Fluw placeholder hides content before client hydration; keep the first frame visible", + ); + } +} + +console.log("No-JS preview visibility verified"); diff --git a/src/app/page.tsx b/src/app/page.tsx index a96ad06..180a62c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,3 @@ -"use client"; - -import { motion } from "framer-motion"; - function FluwSquareIcon({ className = "size-11" }: { className?: string }) { return (
- +
@@ -36,7 +27,7 @@ export default function Home() {
- +