Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ vars.ps1
# Claude Code local settings
.claude/settings.local.json
__pycache__/

# vendored shared docusaurus config (cloned at build time by the vendor-config script)
.shared-config
55 changes: 39 additions & 16 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,18 @@ Consequences:

- Every emitted link, asset path and canonical URL carries the
`/docs/query-library/` prefix, so pages work when proxied.
- Browsing the raw subdomain directly shows broken asset paths - expected and
accepted; canonicalisation is via the `<link rel="canonical">` tags
Docusaurus emits, not redirects. The origin must answer 200 to the proxy -
never add a blanket 301 to stackql.io here.
- Browsing the raw subdomain or a Netlify deploy preview directly works
too, by two cooperating pieces: a non-forced 200 rewrite in
[netlify.toml](netlify.toml) maps the prefixed asset and page paths the
HTML emits back to the origin root, and an inline head script
(`headTags` in docusaurus.config.js) sends a prefix-less pathname to the
prefixed URL before render, because the client router only knows routes
under baseUrl and would otherwise swap the server-rendered page for Not
Found on hydration. The proxy never sends prefixed paths and the browser
URL there always carries the prefix, so neither piece fires in
production. Canonicalisation is via the `<link rel="canonical">` tags
Docusaurus emits, not redirects. The origin must answer 200 to the proxy
- never add a server-side redirect from the root here.
- The committed machine artifacts land in the build at
`build/docs/query-library/` (static copy) while HTML lands at the build
root; [netlify.toml](netlify.toml) has non-forced 200 rewrites that surface
Expand Down Expand Up @@ -137,18 +145,33 @@ easy to get wrong:

## Site chrome (must look identical to stackql.io)

The navbar and footer mirror the main site's config so the proxied pages
read as one site. Main-site destinations (Install, Providers, Blog, the
docs dropdown items, footer links, the sidebar "Back to docs" link) are NOT
external `href` links - each has a redirect stub page under `src/pages/`
mounting [src/components/ExternalRedirect](src/components/ExternalRedirect/index.jsx),
which gives the link a real internal route (no external-link icon, passes
the broken-link checker, works on localhost and on the raw subdomain) and
instantly forwards to the real page on stackql.io. The stub route list, the
navbar/footer `to` values (`mainSitePaths` in docusaurus.config.js) and the
stub files must stay in lockstep with each other and with the main repo's
navbar/footer. Stub routes are noindexed, excluded from the sitemap and
from structured-data JSON-LD. The footer is the swizzled main-site footer
The navbar, footer and every cross-site link come from the shared StackQL
chrome repo, `stackql/docusaurus-config` (local checkout
`../docusaurus-config`), which also drives the provider microsites. The
`vendor-config` script in package.json shallow-clones its `main` into the
gitignored `.shared-config/` before every `yarn start`/`yarn build` (Yarn 1
runs the pre-scripts, so the Netlify build is covered). A failed clone fails
the build by design, and `main` is unpinned, so a shared change goes live on
this site's next build. This site cannot use the shared `createConfig`
factory (it assumes a microsite at baseUrl `/` with its own preset), so
[docusaurus.config.js](docusaurus.config.js) composes the pieces instead:
`buildNavbar()`/`buildFooter()` for the chrome (logo href overridden to the
brand home; `selfUrl` tells the shared code that AI Agents > Query Library
is this site, so it becomes an internal link and its redirect route is not
registered - that page would build to `docs/query-library.html`, which
Netlify's pretty URLs would serve in place of the baseUrl root on direct
hits), `redirectsPlugin` for the main-site destinations
(one local route under baseUrl per link that client-side-forwards to the
real page, so links are internal here: no external-link icon, they pass the
broken-link checker, and they work on localhost and on the raw subdomain)
and `redirectRoutes(baseUrl)` to keep those stub routes out of the sitemap
and of structured-data JSON-LD. The shared Redirect pages carry a canonical
to their target and a zero-second meta refresh and are deliberately not
noindexed (noindex plus canonical is a contradictory signal, and a redirect
is never indexed). There are no `src/pages/` stubs; menu changes belong in the shared
repo, whose README documents the composition contract ("Composing instead
of createConfig") and whose menus must be kept in step with the main site's
navbar/footer. The footer is the swizzled main-site footer
(`src/theme/Footer`, needs `@iconify/react`, `@mui/material`, `clsx`);
`src/css/global.css` is the full main-site stylesheet for visual parity.
DocSearch is enabled only when the `ALGOLIA_*` env vars are set (shared
Expand Down
217 changes: 61 additions & 156 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,46 @@ const {themes} = require('prism-react-renderer');
const darkCodeTheme = themes.dracula;
const nightOwlCodeTheme = themes.nightOwl;

// The navbar and footer mirror the main stackql.io site so the proxied
// library pages read as one site. Every main-site destination has a stub
// page under src/pages/ (rendering src/components/ExternalRedirect) so the
// links are internal routes here - no external-link icon, and the
// broken-link checker validates them. The `to` values below are
// baseUrl-relative; keep them in lockstep with the stub files and with the
// main repo's navbar/footer config.
const mainSitePaths = [
'/install',
'/stackql-deploy',
'/contact-us',
'/stackqldocs',
'/blog',
'/tutorials',
'/docs',
'/docs/command-line-usage/mcp',
'/docs/mcp',
'/docs/mcp/embedded',
'/providers',
'/providers/aws',
'/providers/azure',
'/providers/google',
'/providers/databricks',
'/providers/snowflake',
'/providers/confluent',
'/providers/okta',
'/providers/github',
'/providers/openai',
'/providers/cloudflare',
];
// Full public route paths of the stubs (baseUrl + path): kept out of the
// sitemap and of structured-data JSON-LD emission below.
const redirectStubRoutes = mainSitePaths.map((p) => `/docs/query-library${p}`);
// The navbar, footer and every cross-site link come from the shared StackQL
// chrome (github.com/stackql/docusaurus-config), vendored into the
// gitignored .shared-config/ folder by the `vendor-config` script before
// every start and build - the same wiring the provider microsites use. One
// repo defines the header, footer and menus for every StackQL property, so
// the proxied library pages read as one site with stackql.io. This site
// cannot use the shared createConfig factory (it assumes a microsite at
// baseUrl '/' with its own preset), so the pieces are composed here: see
// "Composing instead of createConfig" in the shared README. The shared
// redirects plugin registers a local route under baseUrl for each main-site
// destination (so the links are internal here - no external-link icon, and
// the broken-link checker validates them) that client-side-forwards to the
// real page.
const shared = require('./.shared-config/index.js');

const baseUrl = '/docs/query-library/';

// selfUrl tells the shared chrome which destination IS this site: its menu
// entry (AI Agents > Query Library) becomes an internal link to the landing
// page, and its redirect route is not registered (a page redirecting to its
// own site, whose built file docs/query-library.html would otherwise shadow
// the baseUrl root on direct hits via Netlify's pretty URLs). The shared
// code owns the comparison; nothing here names the label or path.
const selfUrl = `https://stackql.io${baseUrl}`;

const providerDropDownListItems = [
{label: 'AWS', to: '/providers/aws'},
{label: 'Azure', to: '/providers/azure'},
{label: 'Google', to: '/providers/google'},
{label: 'Databricks', to: '/providers/databricks'},
{label: 'Snowflake', to: '/providers/snowflake'},
{label: 'Confluent', to: '/providers/confluent'},
{label: 'Okta', to: '/providers/okta'},
{label: 'GitHub', to: '/providers/github'},
{label: 'OpenAI', to: '/providers/openai'},
{label: 'Cloudflare', to: '/providers/cloudflare'},
{label: '... More', to: '/providers'},
];
// Full public route paths of the shared redirect stubs: kept out of the
// sitemap and of structured-data JSON-LD emission below.
const redirectStubRoutes = shared.redirectRoutes(baseUrl, {selfUrl});

const footerStackQLItems = [
{label: 'Documentation', to: '/stackqldocs'},
{label: 'Install', to: '/install'},
{label: 'Contact us', to: '/contact-us'},
];
// The site logo goes to the brand home, not this site's root (the shared
// default suits a microsite whose root is its own landing page). An
// external href renders without an icon; target keeps it in the same tab.
const logo = {
...shared.buildNavbar().logo,
href: 'https://stackql.io/',
target: '_self',
};

const footerMoreItems = [
{label: 'Providers', to: '/providers'},
{label: 'stackql-deploy', to: '/stackql-deploy'},
{label: 'Blog', to: '/blog'},
{label: 'Tutorials', to: '/tutorials'},
];
const navbar = {...shared.buildNavbar({selfUrl}), logo};
const footer = {...shared.buildFooter({selfUrl}), logo};

/** @type {import('@docusaurus/types').Config} */
const config = {
Expand All @@ -86,7 +66,7 @@ const config = {
// paths - expected; canonical tags point at stackql.io. These two values
// and the main repo's redirect must agree forever.
url: 'https://stackql.io',
baseUrl: '/docs/query-library/',
baseUrl,

onBrokenLinks: 'throw',
favicon: 'favicon.ico',
Expand All @@ -95,6 +75,21 @@ const config = {
baseUrlIssueBanner: false,
trailingSlash: false,
headTags: [
{
// Direct hits on this origin (Netlify deploy previews, the raw
// query-library.stackql.io host) arrive WITHOUT the baseUrl prefix:
// the HTML the server returns is right (netlify.toml maps the prefixed
// asset paths), but the client router only knows routes under
// baseUrl, so on hydration it matches nothing and swaps the page for
// Not Found - a flash of content, then a 404. Send such a hit to the
// prefixed URL before anything renders. Via the stackql.io proxy the
// pathname always carries the prefix, so this never fires there. A
// server-side redirect cannot do this: the proxy strips the prefix and
// needs the origin to keep answering 200 at the root.
tagName: 'script',
attributes: {},
innerHTML: `(function(){var b='${baseUrl.replace(/\/$/, '')}';var p=location.pathname;if(p.indexOf(b+'/')===0)return;location.replace(b+(p===b?'/':p)+location.search+location.hash)})()`,
},
{
tagName: 'link',
attributes: {
Expand All @@ -112,6 +107,9 @@ const config = {
},
],
plugins: [
// Local redirect routes for every shared main-site destination except
// this site itself (see the selfUrl note at the top of this file).
[shared.redirectsPlugin, {selfUrl}],
'@stackql/docusaurus-plugin-structured-data',
[
'@stackql/docusaurus-plugin-aeo',
Expand Down Expand Up @@ -170,8 +168,9 @@ const config = {
({
docs: false,
blog: false,
// src/pages holds only the redirect stubs for main-site nav targets.
pages: {},
// No src/pages: the main-site redirect stubs are routes registered
// by the shared redirects plugin.
pages: false,
sitemap: {
changefreq: 'weekly',
priority: 0.5,
Expand Down Expand Up @@ -289,102 +288,8 @@ const config = {
hideable: true,
},
},
navbar: {
logo: {
alt: 'StackQL',
// Same behavior as the main site's logo (-> home). External href
// renders without an icon; target keeps it in the same tab.
href: 'https://stackql.io/',
target: '_self',
src: 'img/logo-original.svg',
srcDark: 'img/logo-white.svg',
},
items: [
{
to: '/install',
label: 'Install',
position: 'left',
},
{
type: 'dropdown',
label: 'AI Agents',
position: 'left',
items: [
{
to: '/docs/command-line-usage/mcp',
label: 'MCP Server',
},
{
to: '/docs/mcp',
label: 'MCP Tools',
},
{
to: '/docs/mcp/embedded',
label: 'Embedded MCP',
},
{
// The one destination that IS this site: the library landing.
to: '/',
label: 'Query Library',
},
],
},
{
to: '/stackql-deploy',
label: 'stackql-deploy',
position: 'left',
},
{
to: '/providers',
type: 'dropdown',
label: 'Providers',
position: 'left',
items: providerDropDownListItems,
},
{
type: 'dropdown',
label: 'More',
position: 'left',
items: [
{
to: '/blog',
label: 'Blog',
},
{
to: '/tutorials',
label: 'Tutorials',
},
],
},
{
href: 'https://github.com/stackql/stackql',
position: 'right',
className: 'header-github-link',
'aria-label': 'GitHub repository',
},
],
},
footer: {
style: 'dark',
logo: {
alt: 'StackQL',
href: 'https://stackql.io/',
target: '_self',
src: 'img/logo-original.svg',
srcDark: 'img/logo-white.svg',
},
links: [
{
title: 'StackQL',
items: footerStackQLItems,
},
{
title: 'More',
items: footerMoreItems,
},
],
copyright: `© ${new Date().getFullYear()} StackQL Studios ABN 65 656 147 054`,
},
navbar,
footer,
colorMode: {
respectPrefersColorScheme: true,
},
Expand Down
15 changes: 15 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@
[build.environment]
PYTHON_VERSION = "3.12"

# --- Direct hits on this origin (deploy previews, the raw subdomain) ---
# Proxied requests arrive with the /docs/query-library prefix already
# stripped, but a browser on a deploy preview or on query-library.stackql.io
# itself follows the emitted links and asset paths, which all carry the
# prefix (baseUrl). Map them back to the root so previews render styled and
# navigable. Not forced: the committed artifact tree really exists at
# build/docs/query-library/ (index.json, manifest.json, queries/*.json|md)
# and those real files win, exactly as they do for the proxied path. The
# proxy never sends a prefixed path to this origin, so this rule has no
# effect on production traffic.
[[redirects]]
from = "/docs/query-library/*"
to = "/:splat"
status = 200

# --- Contract artifact rewrites (machine catalogue at the origin root) ---
# Public URLs (via the proxy): stackql.io/docs/query-library/{manifest.json,
# index.json, index.md, providers.json, queries/<id>.{json,md}}.
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"vendor-config": "rimraf .shared-config && git clone --depth 1 --branch main https://github.com/stackql/docusaurus-config.git .shared-config",
"prestart": "yarn vendor-config",
"prebuild": "yarn vendor-config",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
Expand All @@ -22,7 +25,7 @@
"@mui/icons-material": "^5.2.0",
"@mui/material": "^5.2.5",
"@stackql/docusaurus-plugin-aeo": "^0.4.2",
"@stackql/docusaurus-plugin-structured-data": "^1.5.1",
"@stackql/docusaurus-plugin-structured-data": "^1.6.0",
"clsx": "^1.1.1",
"prism-react-renderer": "^2.1.0",
"react": "^18.2.0",
Expand All @@ -39,5 +42,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"rimraf": "^6.0.1"
}
}
Loading
Loading