Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ CMS_INTERNAL_ORIGIN=http://127.0.0.1:5099
# Production uses https://media.sgf.dev/ without a /media/ prefix.
MEDIA_PUBLIC_URL=http://127.0.0.1:5099/media/

# Private runtime flags. Leave disabled outside production.
# SEARCH_INDEXING_ENABLED controls robots.txt and the noindex metadata.
SEARCH_INDEXING_ENABLED=false
# ANALYTICS_ENABLED loads the legacy Plausible tracker only when true.
ANALYTICS_ENABLED=false

# Adapter-node request cap: 8 MiB avatar plus multipart overhead.
BODY_SIZE_LIMIT=9M

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ privately, never in Git, client code or Docker build arguments:
`https://media.sgf.dev/`. Use `http://127.0.0.1:5099/media/` locally. Native CMS
media paths map to that base; existing public media URLs keep their crop and
sizing parameters. See `docs/media.md`.
- `SEARCH_INDEXING_ENABLED` is a private runtime flag, defaulting to `false`.
Set it to `true` only on production sites that should be indexed. It controls
both `/robots.txt` and the page's `noindex, nofollow` metadata.
- `ANALYTICS_ENABLED` is a private runtime flag, defaulting to `false`.
Set it to `true` to load the existing Plausible tracker from `plausible.sgf.dev`.
Neither flag requires a rebuild. Only the exact value `true` enables them.
- Backend `SGFDevs__MemberBridge__FrontendOrigin` must be the frontend origin,
for example `http://127.0.0.1:3000`, so reset emails link to Kit.

Expand Down Expand Up @@ -93,6 +99,10 @@ checks. `PUBLIC_SITE_ORIGIN` controls canonical metadata, not request trust.
Set `BODY_SIZE_LIMIT=9M` for the built server to allow the existing 8 MiB avatar
limit plus multipart overhead. Application validation still limits accepted files.

`GET /healthz` returns plain text `ok\n` with `Cache-Control: no-store`.
It does not call the CMS or look up a member session, even with a member cookie.
Use it for frontend liveness probes, not CMS readiness checks.

## Container and CI

`Dockerfile` builds Node output with the existing lockfile, then installs only
Expand Down
9 changes: 6 additions & 3 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ export const variables = defineEnvVars({
return url.origin;
}
},
PUBLIC_SEARCH_INDEXING_ENABLED: {
description: 'Set to true only for production builds that should be indexed. Missing or false emits noindex.',
public: true,
ANALYTICS_ENABLED: {
description: 'Private runtime flag. Set to true to load the Plausible tracker. Defaults to false.',
schema: (value) => value === 'true'
},
SEARCH_INDEXING_ENABLED: {
description: 'Private runtime flag. Set to true only on production sites that should be indexed. Defaults to false.',
schema: (value) => value === 'true'
}
});
2 changes: 2 additions & 0 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { memberCookieHeader } from './lib/server/auth/cookies';

export const handle: Handle = async ({ event, resolve }) => {
event.locals.member = null;
// Health probes must not depend on CMS sessions, even with a member cookie.
if (event.url.pathname === '/healthz') return resolve(event);
const hasCookie = Boolean(memberCookieHeader(event.cookies, memberCookieName));
if (hasCookie) {
try {
Expand Down
7 changes: 6 additions & 1 deletion src/routes/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { ANALYTICS_ENABLED, SEARCH_INDEXING_ENABLED } from '$app/env/private';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = ({ locals }) => ({ member: locals.member });
export const load: LayoutServerLoad = ({ locals }) => ({
member: locals.member,
analyticsEnabled: ANALYTICS_ENABLED,
searchIndexingEnabled: SEARCH_INDEXING_ENABLED
});
3 changes: 3 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
</script>

<svelte:head>
{#if data.analyticsEnabled}
{@html `<script async src="https://plausible.sgf.dev/js/pa-aOg8YJ_yiLgBbrOwNU2_7.js"></script><script>window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}}; plausible.init()</script>`}
{/if}
{#if !contentPage || contentPage.description}
<meta name="description" content={contentPage?.description ?? 'Springfield Devs is a community of software developers in Springfield, Missouri.'} />
{/if}
Expand Down
5 changes: 3 additions & 2 deletions src/routes/+layout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PUBLIC_SEARCH_INDEXING_ENABLED, PUBLIC_SITE_ORIGIN } from '$app/env/public';
import { PUBLIC_SITE_ORIGIN } from '$app/env/public';
import { canonicalUrlForPathname, siteOrigin } from '../lib/shell/canonical-url';
import type { LayoutLoad } from './$types';

Expand All @@ -9,6 +9,7 @@ export const load: LayoutLoad = ({ url, data }) => {
canonicalUrl: canonicalUrlForPathname(origin, url.pathname),
ogImageUrl: new URL('/images/og.jpg', origin).href,
pageTitle: 'Springfield Devs',
searchIndexingEnabled: PUBLIC_SEARCH_INDEXING_ENABLED === true
searchIndexingEnabled: data.searchIndexingEnabled,
analyticsEnabled: data.analyticsEnabled
};
};
6 changes: 6 additions & 0 deletions src/routes/healthz/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = () => text('ok\n', {
headers: { 'Cache-Control': 'no-store' }
});
8 changes: 8 additions & 0 deletions src/routes/robots.txt/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SEARCH_INDEXING_ENABLED } from '$app/env/private';
import { text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = () => text(
`User-agent: *\n${SEARCH_INDEXING_ENABLED ? 'Allow: /' : 'Disallow: /'}\n`,
{ headers: { 'Cache-Control': 'no-store' } }
);
Loading