yoursite.com/docs, instead of a separate subdomain. This keeps your docs and product on the same domain for a seamless user experience and better SEO.yoursite.com/docs instead of docs.yoursite.com means all search ranking authority stays on your main domain.yoursite.com/docs/quickstart instead of docs.yoursite.com/quickstart.--base-path to your deploy command. The path is the subpath prefix where your docs will live:1HOLOCRON_KEY=holo_xxx npx -y @holocron.so/cli deploy --base-path /docs
/docs/ and deploys it to your holocron.so URL. Your deployed site serves pages at /docs/quickstart, /docs/api-reference, etc.1https://docs-base-my-docs-remorses-site.holocron.so
{base}-base-{project}-site.holocron.so) so they coexist with root deployments on the same project. Your docs are accessible at the printed URL under the /docs/ path./docs/* to the deployed holocron.so URL. The user's browser sees yoursite.com/docs/quickstart, but the content is served from holocron.so.12345678┌──────────────────────┐ ┌──────────────────────────────────────────────┐ │ yoursite.com │ │ holocron.so hosting │ │ │ │ │ │ / → home │ │ │ │ /pricing → page │ │ │ │ /docs/* → proxy ├──────►│ docs-base-my-docs-remorses-site.holocron.so │ │ │ │ serves /docs/* routes │ └──────────────────────┘ └──────────────────────────────────────────────┘
next.config.js to forward /docs requests to your deployed URL:1234567891011121314151617// next.config.js const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so' module.exports = { async rewrites() { return [ { source: '/docs', destination: `${DOCS_URL}/docs`, }, { source: '/docs/:path*', destination: `${DOCS_URL}/docs/:path*`, }, ] }, }
vercel.json:123456789101112{ "rewrites": [ { "source": "/docs", "destination": "https://docs-base-my-docs-remorses-site.holocron.so/docs" }, { "source": "/docs/:path*", "destination": "https://docs-base-my-docs-remorses-site.holocron.so/docs/:path*" } ] }
app/routes/docs.$.ts. A resource route has no default export, so React Router treats it as a pure server endpoint. The loader handles GET requests and action handles everything else:12345678910111213141516171819202122// app/routes/docs.$.ts const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so' export async function loader({ request }: { request: Request }) { const url = new URL(request.url) const target = new URL(url.pathname + url.search, DOCS_URL) return fetch(target, { headers: { 'X-Forwarded-Host': url.hostname, }, }) } export async function action({ request }: { request: Request }) { const url = new URL(request.url) const target = new URL(url.pathname + url.search, DOCS_URL) return fetch(target, { method: request.method, headers: request.headers, body: request.body, }) }
app/routes/docs._index.ts so bare /docs is handled:12// app/routes/docs._index.ts export { loader, action } from './docs.$.ts'
app/routes/docs/$.ts. TanStack Start uses createAPIFileRoute from @tanstack/react-start/api for server-only routes that return raw Response objects. The file path docs/$.ts maps to the /docs/$ catch-all:123456789101112131415161718192021// app/routes/docs/$.ts import { createAPIFileRoute } from '@tanstack/react-start/api' const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so' function proxy(request: Request) { const url = new URL(request.url) const target = new URL(url.pathname + url.search, DOCS_URL) return fetch(target, { method: request.method, headers: { 'X-Forwarded-Host': url.hostname }, body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined, }) } export const APIRoute = createAPIFileRoute('/docs/$')({ GET: ({ request }) => proxy(request), POST: ({ request }) => proxy(request), })
_splat param captures everything after /docs/, but since the proxy forwards url.pathname directly, it works without parsing it./docs requests:12345678910111213141516export default { async fetch(request) { const url = new URL(request.url) if (url.pathname === '/docs' || url.pathname.startsWith('/docs/')) { const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so' const proxyUrl = new URL(url.pathname + url.search, DOCS_URL) const proxyReq = new Request(proxyUrl, request) proxyReq.headers.set('X-Forwarded-Host', url.hostname) return fetch(proxyReq) } // Pass through to your origin for everything else return fetch(request) }, }
location block to your Nginx config:12345678910location /docs/ { proxy_pass https://docs-base-my-docs-remorses-site.holocron.so/docs/; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; } location = /docs { return 301 /docs/; }
http-proxy-middleware in your Express app:123456789const { createProxyMiddleware } = require('http-proxy-middleware') app.use( '/docs', createProxyMiddleware({ target: 'https://docs-base-my-docs-remorses-site.holocron.so', changeOrigin: true, }), )
--base-path in your CI workflow the same way:123456789101112131415161718name: Deploy docs on: push: branches: [main] permissions: id-token: write jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm install - run: npx -y @holocron.so/cli deploy --base-path /docs
base option directly instead of using --base-path:12345678// vite.config.ts import { defineConfig } from 'vite' import { holocron } from '@holocron.so/vite' export default defineConfig({ base: '/docs/', plugins: [holocron()], })
/docs/* to the docs server.