Skip to content
Open
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
14 changes: 14 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ npm run preview # Preview build

Playwright tests live in `tests/speaking-layout.spec.js`. They validate carousel positioning, spacing, and responsive behavior. **Tests must pass before merging.**

## Blog Drafts

Posts with `draft: true` in their frontmatter are excluded from `/blog/`, `/rss.xml`, and the sitemap. They build to `/blog/draft/[slug]/` so they can be shared for feedback without going public.

The path `/blog/draft/*` is gated by a Netlify Edge Function (`netlify/edge-functions/draft-auth.js`) that requires HTTP Basic Auth. Credentials are read from `DRAFT_USER` and `DRAFT_PASSWORD` env vars in the Netlify dashboard (Site settings → Environment variables). If either is unset, the gate returns `503` for all draft requests.

Workflow:
1. Add a `.md` to `src/content/blog/` with `draft: true` in frontmatter.
2. Push the branch — Netlify deploy preview builds the draft page.
3. Share the URL with the reviewer; they'll be prompted for Basic Auth credentials.
4. Flip `draft: false` (and remove or update `pubDate`) to publish.

Locally (`npm run dev`), drafts are accessible at `/blog/draft/[slug]/` without auth, since edge functions only run on Netlify.

## AI Behavioral Rules

1. **Prioritize simplicity** — if there's a simpler way, use it
Expand Down
16 changes: 15 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
// @ts-check
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

// https://astro.build/config
export default defineConfig({});
export default defineConfig({
site: 'https://javirubio.net',
integrations: [
sitemap({
filter: (page) => !page.includes('/blog/draft/'),
}),
],
markdown: {
shikiConfig: {
theme: 'github-light',
wrap: true,
},
},
});
39 changes: 39 additions & 0 deletions netlify/edge-functions/draft-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Basic Auth gate for /blog/draft/* — runs on Netlify Edge.
// Configure DRAFT_USER and DRAFT_PASSWORD as environment variables in the
// Netlify dashboard. If either is unset, all draft requests are denied.

export default async (request, context) => {
const user = Netlify.env.get('DRAFT_USER');
const password = Netlify.env.get('DRAFT_PASSWORD');

if (!user || !password) {
return new Response('Drafts disabled: credentials not configured.', {
status: 503,
});
}

const header = request.headers.get('Authorization') ?? '';
if (header.startsWith('Basic ')) {
const decoded = atob(header.slice(6));
const sep = decoded.indexOf(':');
if (sep !== -1) {
const providedUser = decoded.slice(0, sep);
const providedPassword = decoded.slice(sep + 1);
if (providedUser === user && providedPassword === password) {
return context.next();
}
}
}

return new Response('Authentication required.', {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="Drafts", charset="UTF-8"',
'Cache-Control': 'no-store',
},
});
};

export const config = {
path: '/blog/draft/*',
};
Loading