# Subdirectory Hosting — Reverse Proxy Setup Guide

This guide explains how to serve your Inkhost blog under a path on your main website (e.g. `example.com/blog`) using a reverse proxy. The blog is hosted on Inkhost's infrastructure; your main site simply proxies requests for `/blog/*` to Inkhost.

Once you have your proxy configured, enter your **public host** (`example.com`) and **path** (`/blog`) in the blog's **Subdirectory settings** card in the Inkhost dashboard.

---

## Recommended: Vercel (main site on Vercel)

Add a rewrite in your main site's `vercel.json`. Target your blog's **own subdomain** on Inkhost, not a bare app domain — this is how Inkhost resolves which blog to serve.

```json
{
  "rewrites": [
    {
      "source": "/blog/:path*",
      "destination": "https://<your-slug>.inkhost.app/:path*"
    }
  ]
}
```

Replace `<your-slug>` with the slug shown in your Inkhost dashboard (e.g. `acme.inkhost.app`).

**How it works:** Vercel proxies requests at `example.com/blog/*` to your Inkhost subdomain. Inkhost detects the subdirectory configuration and emits canonical URLs at `example.com/blog/…` rather than the underlying subdomain.

> **Note on Vercel Deployment Protection:** If your Inkhost production deployment has Vercel Deployment Protection (password or team auth) enabled, proxied requests will be blocked. Either disable Deployment Protection for the production deployment, or pass a [Protection Bypass header](https://vercel.com/docs/security/deployment-protection/methods-to-bypass-deployment-protection) from your rewrite.

---

## Cloudflare Workers

```js
export default {
  async fetch(request) {
    const url = new URL(request.url);

    if (url.pathname.startsWith("/blog")) {
      const target = new URL(url.pathname + url.search, "https://<your-slug>.inkhost.app");
      return fetch(target.toString(), {
        method: request.method,
        headers: request.headers,
        body: request.body,
      });
    }

    // Pass through all other requests to your origin
    return fetch(request);
  },
};
```

Replace `<your-slug>` with your Inkhost subdomain slug. Deploy this Worker and attach it to your domain route in the Cloudflare dashboard.

---

## nginx

```nginx
location /blog {
    proxy_pass https://<your-slug>.inkhost.app;
    proxy_set_header Host <your-slug>.inkhost.app;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_ssl_server_name on;
}
```

Replace `<your-slug>` with your Inkhost subdomain slug. Reload nginx after saving (`nginx -s reload`).

---

## Critical Notes

### 1. robots.txt — sitemap declaration

Add the following line to your **root** `robots.txt` at `example.com/robots.txt`:

```
Sitemap: https://example.com/blog/sitemap.xml
```

A `robots.txt` placed under `/blog/robots.txt` is **ignored** by search engine crawlers — only the root-level file is read. Alternatively, submit the sitemap URL directly in Google Search Console.

### 2. Vercel Deployment Protection

If the Inkhost app's production deployment has Vercel Deployment Protection enabled (team auth, password, or Vercel Authentication), your proxy's requests will be rejected with a 401/403. Disable it for the production deployment, or configure a [Protection Bypass for Automation](https://vercel.com/docs/security/deployment-protection/methods-to-bypass-deployment-protection) header in your proxy.

### 3. Custom Domain and Subdirectory hosting are mutually exclusive

A blog is addressed **one** way. Once subdirectory hosting is configured, the Custom
domain card is disabled (and `/api/domains/set` returns a `409`), and vice versa —
remove one before adding the other. Two canonical hosts for the same content split
link equity between them, waste crawl budget, and let Google pick the address you
didn't want.

### 4. Never link or submit the `<slug>.inkhost.app` address

Your proxy fetches `https://<your-slug>.inkhost.app/*` as its **origin**, so that host
has to keep answering requests directly — Inkhost cannot redirect it away without
breaking your `/blog`. It is deduplicated instead by a cross-domain `rel=canonical`
pointing at `example.com/blog`, which search engines honour.

For that to hold, treat the subdomain as internal plumbing:

- don't link to it from anywhere,
- don't submit it as a property or a sitemap in Search Console,
- use `example.com/blog/...` in every share, newsletter, and social post.

(Blogs on a **custom domain** work differently: there is no proxy, so the subdomain
permanently redirects (`308`) to the custom domain and needs no care.)

### 5. Redirect legacy `/p/` links on your own domain

Requests to `example.com/p/<slug>` never reach Inkhost — your proxy only forwards
`/blog/*`, so they hit your product site and 404. Old links (shared, indexed, or
written into a post body before the base path was configured) therefore need a
redirect on **your** side. Add one of these next to your proxy rule:

**nginx**

```nginx
location ^~ /p/ { return 301 /blog$request_uri; }
```

**Vercel** (`vercel.json`, alongside the rewrite):

```json
{
  "redirects": [
    { "source": "/p/:path*", "destination": "/blog/p/:path*", "permanent": true }
  ]
}
```

**Cloudflare Worker** (before the `/blog` branch):

```js
if (url.pathname.startsWith("/p/")) {
  return Response.redirect(new URL("/blog" + url.pathname + url.search, url), 301);
}
```

Apply the same pattern to `/tag/` and `/pillar/` if you have shared links to those.

### 6. Enter the public host and path in the dashboard last

Set up and verify your proxy is working **before** entering the host and path in the Inkhost dashboard. Once saved, Inkhost begins emitting canonicals and sitemaps at `example.com/blog`. If the proxy is not yet active, crawlers that follow the canonical will get 404s.

---

## Verifying it works

After configuring the proxy and saving the settings in the dashboard:

1. Visit `https://example.com/blog` — you should see your Inkhost blog.
2. View source and confirm `<link rel="canonical" href="https://example.com/blog/…" />` appears on post pages.
3. Open `https://example.com/blog/sitemap.xml` — it should return your blog's sitemap with URLs rooted at `example.com/blog`.
4. Check `https://example.com/robots.txt` includes the sitemap reference.
