chore: add coming soon page
This commit is contained in:
BIN
public/astronaut.png
Normal file
BIN
public/astronaut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
54
src/middleware.ts
Normal file
54
src/middleware.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { defineMiddleware } from 'astro:middleware'
|
||||||
|
|
||||||
|
// Pre-launch gate: clients unlock the real site via a secret link
|
||||||
|
// (https://your-domain/?preview=PREVIEW_SECRET); everyone else — strangers and
|
||||||
|
// bots — is shown src/pages/coming-soon.astro. The gate is active only while
|
||||||
|
// SHOW_COMING_SOON=true; unset it (or set anything else) to take the site live.
|
||||||
|
|
||||||
|
const COOKIE_NAME = 'preview_access'
|
||||||
|
const COOKIE_MAX_AGE = 60 * 60 * 24 * 7 // 7 days
|
||||||
|
const COMING_SOON_ROUTE = '/coming-soon'
|
||||||
|
|
||||||
|
export const onRequest = defineMiddleware((context, next) => {
|
||||||
|
// Prefer process.env so values can be set at deploy/runtime (Docker); fall
|
||||||
|
// back to import.meta.env so local `astro dev` picks them up from .env (Vite
|
||||||
|
// only loads .env into import.meta.env, not process.env).
|
||||||
|
const secret = process.env.PREVIEW_SECRET ?? import.meta.env.PREVIEW_SECRET
|
||||||
|
const showComingSoon =
|
||||||
|
(process.env.SHOW_COMING_SOON ?? import.meta.env.SHOW_COMING_SOON) === 'true'
|
||||||
|
|
||||||
|
// Gate disabled — site is live.
|
||||||
|
if (!showComingSoon) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cookies, url, redirect } = context
|
||||||
|
|
||||||
|
// Magic preview link: ?preview=SECRET remembers the client in a cookie, then
|
||||||
|
// we redirect to the same URL without the token so it isn't visible/forwarded.
|
||||||
|
const previewParam = url.searchParams.get('preview')
|
||||||
|
if (previewParam !== null) {
|
||||||
|
if (secret && previewParam === secret) {
|
||||||
|
cookies.set(COOKIE_NAME, secret, {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: url.protocol === 'https:',
|
||||||
|
path: '/',
|
||||||
|
maxAge: COOKIE_MAX_AGE,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
url.searchParams.delete('preview')
|
||||||
|
return redirect(url.pathname + url.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlocked clients, the launch page, and robots.txt get the real render
|
||||||
|
// (robots.txt must reach bots so it can tell them to stay out while gated).
|
||||||
|
const isClient = Boolean(secret) && cookies.get(COOKIE_NAME)?.value === secret
|
||||||
|
if (isClient || url.pathname === COMING_SOON_ROUTE || url.pathname === '/robots.txt') {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everyone else is shown the launch page while keeping the original URL.
|
||||||
|
// next(path) rewrites internally without re-running this middleware.
|
||||||
|
return next(COMING_SOON_ROUTE)
|
||||||
|
})
|
||||||
96
src/pages/coming-soon.astro
Normal file
96
src/pages/coming-soon.astro
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
// Standalone "Getting ready for launch" page shown to everyone who is NOT an
|
||||||
|
// unlocked client (strangers, search engines, bots). The gate in
|
||||||
|
// src/middleware.ts internally rewrites unauthorized requests to this route.
|
||||||
|
//
|
||||||
|
// It is intentionally self-contained — no shared Layout and no Strapi data — so
|
||||||
|
// it renders even while the rest of the site is gated. Edit the copy in <main>.
|
||||||
|
Astro.response.headers.set('X-Robots-Tag', 'noindex, nofollow')
|
||||||
|
---
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>Скоро запуск</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="dot" aria-hidden="true"></div>
|
||||||
|
<h1>Мы готовимся к запуску</h1>
|
||||||
|
<p>Сайт скоро станет доступен. Спасибо, что заглянули — мы уже совсем близко.</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100%;
|
||||||
|
padding: 24px;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica,
|
||||||
|
Arial, sans-serif;
|
||||||
|
color: #e7e9ee;
|
||||||
|
background: radial-gradient(
|
||||||
|
1200px 600px at 50% -10%,
|
||||||
|
#1b2a4a 0%,
|
||||||
|
#0b1020 55%,
|
||||||
|
#070a14 100%
|
||||||
|
);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 540px;
|
||||||
|
}
|
||||||
|
.dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin: 0 auto 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #5b8cff;
|
||||||
|
animation: pulse 2s ease-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(91, 140, 255, 0.55);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 18px rgba(91, 140, 255, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(91, 140, 255, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 14px;
|
||||||
|
font-size: clamp(28px, 5vw, 40px);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(15px, 2.5vw, 18px);
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #a8b0c2;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.dot {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
src/pages/robots.txt.ts
Normal file
19
src/pages/robots.txt.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { APIRoute } from 'astro'
|
||||||
|
|
||||||
|
// robots.txt driven by the same SHOW_COMING_SOON flag as the pre-launch gate
|
||||||
|
// (see src/middleware.ts). While gated, keep the whole site out of search
|
||||||
|
// indexes; once live, allow crawling. Read at request time so it can be flipped
|
||||||
|
// via the deploy/runtime environment without a rebuild.
|
||||||
|
export const prerender = false
|
||||||
|
|
||||||
|
export const GET: APIRoute = () => {
|
||||||
|
const gated =
|
||||||
|
(process.env.SHOW_COMING_SOON ?? import.meta.env.SHOW_COMING_SOON) === 'true'
|
||||||
|
const body = gated
|
||||||
|
? 'User-agent: *\nDisallow: /\n'
|
||||||
|
: 'User-agent: *\nDisallow:\n'
|
||||||
|
|
||||||
|
return new Response(body, {
|
||||||
|
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user