---
title: "Next.js on Hostinger with GitHub, Cloudflare & AI Search"
author: "Rantideb Howlader"
date: "2026-07-20T00:00:00.000Z"
canonical_url: "https://ranti.dev/blog/hostinger-cloudflare-nextjs-hosting-guide"
license: "CC-BY-4.0"
---


## Introduction

I deliberately chose not to use Vercel for this portfolio. Vercel is excellent. However, free tier limits become an issue quickly for personal projects.

Instead, I built a custom deployment pipeline. It combines three tools:

- **[Hostinger](https://www.hostinger.com/bd?REFERRALCODE=HQ3RANTIDWPM)**: Node.js runtime and hosting.
- **Cloudflare**: DNS, CDN, and DDoS protection.
- **Cloudflare Workers AI & Vectorize**: Semantic AI search.

This post is a hands-on guide. I document exactly how I built this setup. It covers every command, config, and click. Follow these steps to build the same pipeline for your Next.js project.

The stack:

- **Runtime:** Next.js 15 with full SSR.
- **Package manager:** `pnpm`.
- **Hosting:** [Hostinger](https://www.hostinger.com/bd?REFERRALCODE=HQ3RANTIDWPM) Web App deployment.
- **DNS & CDN:** Cloudflare free plan.
- **AI search:** Cloudflare Workers AI (`@cf/baai/bge-small-en-v1.5`) and Vectorize.

## Phase 1: Preparing the Repository

### Step 1: Push Code to GitHub

Hostinger pulls your code directly from a GitHub repository. Push your Next.js project to GitHub before doing anything else.

```bash
git init
git add .
git commit -m "initial commit"
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.git
git push -u origin main
```

Your repository can be public or private. Hostinger handles both formats smoothly.

### Step 2: Set Up `pnpm` as the Package Manager

I use `pnpm` instead of `npm`. This is a hard requirement because of Hostinger's inode limits. I will explain inodes in the next step.

Make sure your project declares `pnpm` as the package manager in `package.json`:

```json
{
  "packageManager": "pnpm@10.0.0"
}
```

You must also commit a `pnpm-lock.yaml` file. Hostinger runs `pnpm install --frozen-lockfile` during the build. The build fails if the lockfile is missing.

```bash
pnpm install
git add pnpm-lock.yaml
git commit -m "add pnpm lockfile"
git push
```

### Step 3: Add a `prebuild` Script for Inode Control

This is the most important lesson I learned with Hostinger. Hostinger limits the total number of files on your account. These files are called inodes.

Next.js generates thousands of tiny files in `.next/cache` and `node_modules/.cache` during every build. You will hit the inode limit after a few deployments. Then, your deployments will fail silently.

The fix is adding a `prebuild` script to `package.json`. This script wipes the cache directories before every build:

```json
{
  "scripts": {
    "prebuild": "rm -rf .next/cache && rm -rf node_modules/.cache",
    "build": "next build",
    "start": "next start"
  }
}
```

Node package managers automatically run any script named `prebuild` before running `build`. Hostinger will clear the old cache before building the new version. This keeps your total file count flat over time.

Commit this change before moving forward:

```bash
git add package.json
git commit -m "add prebuild cache cleanup for inode control"
git push
```

## Phase 2: Deploying on [Hostinger](https://www.hostinger.com/bd?REFERRALCODE=HQ3RANTIDWPM)

### Step 4: Create a Web App in hPanel

1. Log into your Hostinger account and open **hPanel**.
2. Go to **Websites** in the sidebar.
3. Click **Add website** or **Deploy Your Web App**.
4. You will see an **Import Git repository** option. Click **Continue with GitHub**.
5. Authorize Hostinger to access your repositories on the GitHub OAuth page.
6. Return to hPanel and select your repository from the dropdown menu.
7. Select your main branch. Mine is `main`.

### Step 5: Configure the Build Settings

Hostinger detects your Next.js application automatically. It pre-fills the build and start commands. Check them carefully:

| Setting          | Value                        |
| :--------------- | :--------------------------- |
| Build command    | `pnpm install && pnpm build` |
| Start command    | `pnpm start`                 |
| Node.js version  | 22.x                         |
| Output directory | `.next`                      |

Change the commands manually if Hostinger defaults to `npm`.

The `prebuild` script handles the cache cleanup automatically. You do not need to add `rm -rf` commands here.

### Step 6: First Deployment

Click **Deploy**. Hostinger will execute these steps:

1. Clone your repository.
2. Run `pnpm install --frozen-lockfile`.
3. Run the `prebuild` script to clear cache.
4. Run `pnpm build`.
5. Start the server with `pnpm start`.

The first build takes a few minutes. You can monitor the live logs in hPanel. Hostinger provides a temporary `.hostingersite.com` subdomain for testing when the build finishes.

### Step 7: Automatic Deployments

Hostinger creates a GitHub webhook automatically. Any push to your selected branch triggers a new deployment.

Verify this is active:

1. Go to your GitHub repository.
2. Navigate to **Settings** then **Webhooks**.
3. Look for a Hostinger webhook URL with a green checkmark.

Every time I run `git push origin main`, Hostinger pulls the code, builds it, and deploys it automatically.

## Phase 3: Pointing Your Domain Through Cloudflare

I use Cloudflare for three reasons:

- Global CDN caching
- Free automatic HTTPS
- DDoS protection

The traffic flow is simple: User → Cloudflare edge → Hostinger.

### Step 8: Add Your Site to Cloudflare

1. Create a free account at [dash.cloudflare.com](https://dash.cloudflare.com).
2. Click **Add a Site** and enter your domain name.
3. Select the **Free** plan.
4. Cloudflare scans your existing DNS records. Ensure your Hostinger A record is listed. Add it manually if it is missing.

### Step 9: Update Your Domain's Nameservers

Cloudflare assigns you two custom nameservers. They look like this:

```
amy.ns.cloudflare.com
bob.ns.cloudflare.com
```

Go to your domain registrar. Replace the existing nameservers with the Cloudflare nameservers. Save your changes.

DNS propagation usually finishes in under 30 minutes.

### Step 10: Verify DNS Records in Cloudflare

Go to the **DNS** tab in Cloudflare after propagation finishes. Check your records:

| Type  | Name  | Content               | Proxy                  |
| :---- | :---- | :-------------------- | :--------------------- |
| A     | `@`   | Hostinger's server IP | Proxied (orange cloud) |
| CNAME | `www` | `yourdomain.com`      | Proxied (orange cloud) |

The orange cloud icon confirms traffic is routing through Cloudflare.

### Step 11: SSL and Performance Settings

Adjust these settings in Cloudflare:

1. Go to **SSL/TLS**. Set encryption mode to **Full (strict)**.
2. Go to **Speed** then **Optimization**. Enable **Auto Minify** for JavaScript, CSS, and HTML.
3. Go to **Caching** then **Configuration**. Set the Browser Cache TTL to **4 hours**.

Your site now serves over HTTPS globally.

## Phase 4: AI-Powered Blog Search

This portfolio uses semantic vector embeddings. Every blog post is indexed and stored in Cloudflare Vectorize.

I built two AI features using this data:

1. **AI Search (`/api/ai-search`):** Readers ask questions and get relevant blog posts based on meaning, not just keywords.
2. **Per-post AI Chat (`/api/blog-chat`):** An AI assistant on each page answers questions about the specific post.

These features use standard Next.js API routes. They do not require a separate Cloudflare Worker. Hostinger runs the full Node.js runtime, allowing direct REST API calls.

### Step 12: The Indexing Script

I pre-process every blog post and convert the text into vectors. I use a Node.js script located at `scripts/index-blog-embeddings.js`.

The script processes every `.mdx` file:

**1. Parse and clean the text:**

````javascript
const { data, content } = matter(rawFile);

const cleanContent = removeMd(
  content
    .replace(/```[\s\S]*?```/g, "") // strip code blocks
    .replace(/<[\s\S]*?>/g, "") // strip HTML tags
);
````

I strip out code blocks and HTML. I only want the core prose for semantic search.

**2. Split into chunks:**

```javascript
function chunkText(text, minLength = 300, maxLength = 1000) {
  const paragraphs = text.split(/

+/);
  // ... builds chunks that respect paragraph boundaries
}
```

I split each post into paragraph-aware chunks. Each chunk is between 300 and 1000 characters.

**3. Generate embeddings with Cloudflare Workers AI:**

```javascript
const url = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/ai/run/@cf/baai/bge-small-en-v1.5`;

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ text: [chunk] }),
});

const embedding = json.result.data[0]; // 384-dimension float array
```

I use Cloudflare's hosted `bge-small-en-v1.5` model. It is fast and requires no local ML infrastructure.

**4. Upsert into Vectorize:**

```javascript
const url = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/vectorize/v2/indexes/blog-search/upsert`;

const vectors = chunks.map((chunk, j) => ({
  id: `${slug}#${j}`,
  values: embedding,
  metadata: { slug, title, text: chunk, chunkIndex: j },
}));
```

The vector IDs use the format `slug#chunkIndex`. The script detects if a post gets shorter and deletes orphaned vectors automatically.

**5. Content-hash caching:**

```javascript
const contentHash = crypto.createHash("sha256").update(raw, "utf8").digest("hex");

const cached = indexCache[slug];
if (cached && cached.hash === contentHash) {
  skippedCount++;
  continue;  // nothing changed, skip this post entirely
}
```

I use a SHA-256 hash instead of file modification times. Git clone resets file times on every deploy. A hash ensures we only re-index actual content changes.

### Step 13: Why Use GitHub Actions?

Many people ask why I run embeddings in GitHub Actions instead of Hostinger. There are three reasons:

**1. Build Time Limits.** Generating embeddings requires remote API calls. Doing this during the Hostinger build would add minutes to the process and cause timeouts.

**2. Avoiding Redundant Indexing.** Hostinger runs a fresh git clone on every deploy. Running the script there would trigger unnecessary API calls. GitHub Actions only triggers when an `.mdx` file changes.

**3. Separation of Concerns.** Hostinger serves the application. Cloudflare stores the index. The indexing script is a background data migration job. It belongs in CI.

### Step 14: The GitHub Actions Workflow

The workflow file is located at `.github/workflows/index-blog-embeddings.yml`:

```yaml
name: Index Blog Embeddings

on:
  push:
    branches: ["main"]
    paths:
      - "content/**/*.mdx"

concurrency:
  group: index-blog-embeddings-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: write

jobs:
  index:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v3
        with:
          version: 10

      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "pnpm"

      - run: pnpm install --frozen-lockfile

      - name: Index new/changed blog posts
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
        run: pnpm index:embeddings

      - name: Commit updated cache
        run: |
          if ! git diff --quiet -- scripts/.indexed-posts-cache.json; then
            git config user.name "github-actions[bot]"
            git config user.email "github-actions[bot]@users.noreply.github.com"
            git add scripts/.indexed-posts-cache.json
            git commit -m "chore: update blog embeddings cache [skip ci]"
            git push
          else
            echo "No cache changes to commit."
          fi
```

The `[skip ci]` tag is essential. It prevents the bot commit from triggering an infinite loop of GitHub Actions.

### Step 15: Setting Up Cloudflare Secrets

Add these secrets to GitHub under **Settings > Secrets and variables > Actions**:

| Secret name             | How to get it                                                                                                             |
| :---------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `CLOUDFLARE_API_TOKEN`  | Cloudflare Dashboard → My Profile → API Tokens → Create Token → select **Cloudflare AI**, **Vectorize** (Edit permission) |
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Dashboard → home page → right sidebar, listed under "Account ID"                                               |

### Step 16: First Run

Run the indexer locally the first time to bootstrap the database:

```bash
CLOUDFLARE_API_TOKEN=your_token \
CLOUDFLARE_ACCOUNT_ID=your_account_id \
pnpm index:embeddings
```

The script will create the `blog-search` index and upload all vectors. Commit the cache file when it finishes:

```bash
git add scripts/.indexed-posts-cache.json
git commit -m "chore: initial blog embeddings cache"
git push
```

### Step 17: The AI Search Route

The `/api/ai-search` route handles user queries. It performs three steps:

**Step A: Embed the query**

```typescript
const embeddingUrl = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/ai/run/@cf/baai/bge-small-en-v1.5`;

const embeddingRes = await fetch(embeddingUrl, {
  method: "POST",
  headers: { Authorization: `Bearer ${API_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ text: [query] }),
});

const queryVector = embeddingJson.result.data[0];
```

**Step B: Query Vectorize**

```typescript
const vectorizeUrl = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/vectorize/v2/indexes/blog-search/query`;

const vectorizeRes = await fetch(vectorizeUrl, {
  method: "POST",
  headers: { Authorization: `Bearer ${API_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    vector: queryVector,
    topK: 8,
    returnValues: false,
    returnMetadata: "all",
  }),
});
```

**Step C: Group and return**

The route groups matching chunks by post slug. It ranks them by similarity score and returns the results to the frontend.

### Step 18: The Per-Post AI Chat Route

The `/api/blog-chat` route powers the chat widget on each post. It reads the MDX file from disk instead of querying Vectorize.

**Read and clean the post:**

````typescript
const rawFile = fs.readFileSync(filePath, "utf8");
const { data, content } = matter(rawFile);

const cleanContent = removeMd(content.replace(/```[\s\S]*?```/g, "").replace(/<[\s\S]*?>/g, ""));
````

**Build the system prompt:**

```typescript
const systemPrompt = `
You are Ranti's Blog AI assistant. Your goal is to help the reader understand the blog post titled "${title}".
Speak in the first person (as Ranti, the author of this post). Warm, friendly, professional.

Here is the complete text of the blog post:
=========================================
Title: ${title}
Date: ${data.publishedAt}
Content:
${cleanContent}
=========================================

Instructions:
- Answer questions directly related to this blog post.
- Always speak in the first person (e.g. "In my article...", "I wrote this because...").
- Keep answers helpful, concise, and focused on the article.
`;
```

**Send to Cloudflare Workers AI:**

```typescript
const model = "@cf/meta/llama-3.1-8b-instruct";
const url = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/ai/run/${model}`;

const response = await fetch(url, {
  method: "POST",
  headers: { Authorization: `Bearer ${API_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    messages: [{ role: "system", content: systemPrompt }, ...messages],
    max_tokens: maxTokens,
  }),
});
```

I adjust `max_tokens` dynamically based on the query complexity. This saves Cloudflare AI quota on simple questions.

## How It All Fits Together

Here is the data flow for the AI search:

```mermaid
flowchart TD
    A[Reader types a search query] --> B[Next.js /api/ai-search route on Hostinger]
    B --> C[Cloudflare Workers AI\nbge-small-en-v1.5\nEmbed the query into a 384-dim vector]
    C --> D[Cloudflare Vectorize\nFind top-8 matching post chunks\nby cosine similarity]
    D --> E[Group chunks by post slug\nRank by highest similarity score]
    E --> F[Return ranked post list with snippets\nto the frontend]
```

Here is the data flow for the per-post AI chat:

```mermaid
flowchart TD
    A[Reader asks a question on a blog post page] --> B[Next.js /api/blog-chat route on Hostinger]
    B --> C[Read the full MDX post from disk\nStrip markdown and code blocks]
    C --> D[Build system prompt\nFull post text injected as context]
    D --> E[Cloudflare Workers AI\nLlama 3.1 8B Instruct\nGenerate a grounded answer]
    E --> F[Return AI response to the reader]
```

Here is the overall deployment and indexing architecture:

```mermaid
flowchart LR
    subgraph GitHub
        A[Push to main] --> B{Which files changed?}
        B -- MDX file changed --> C[GitHub Actions\nIndex Blog Embeddings workflow]
        B -- Code changed --> D[Hostinger webhook\ntriggers redeploy]
    end

    subgraph Cloudflare
        C --> E[Workers AI\nbge-small-en-v1.5\nGenerate embeddings]
        E --> F[Vectorize\nblog-search index\nStore vectors]
        C --> G[Commit updated cache\nwith skip ci]
    end

    subgraph Hostinger
        D --> H[pnpm install\nprebuild clears cache\npnpm build\npnpm start]
        H --> I[Next.js running\nSSR and API routes live]
    end

    I -- Search query --> E
    F -- Top-k matches --> I
```

## Conclusion

This complete stack provides a production-grade, globally distributed, AI-powered portfolio.

- **Hostinger:** Node.js runtime.
- **Cloudflare:** DNS, CDN, AI, and Vectorize.
- **GitHub Actions:** Automated indexing.

The core insight is that Cloudflare's AI tools are REST APIs. You can call them from any server, including a Next.js API route on [Hostinger](https://www.hostinger.com/bd?REFERRALCODE=HQ3RANTIDWPM).

- **Deployments:** Fully automated on every push.
- **Inode safety:** Handled by the `prebuild` script and `pnpm`.
- **AI indexing:** Runs purely in GitHub Actions.
- **Search and Chat:** Powered by Next.js API routes hitting Cloudflare.

I highly recommend this setup if you want full control and AI capabilities without Vercel lock-in.

## FAQ

### Can you host Next.js on Hostinger?

Yes. Hostinger's Deploy Your Web App feature supports full Node.js runtimes. You can deploy Next.js with SSR, API routes, and server components directly. Connect your GitHub repository, and Hostinger handles the build.

### Does Hostinger support Node.js and Next.js natively?

Yes. Hostinger runs Node.js natively on its Web Hosting plans. It supports Next.js, Nuxt, and Express. You do not need a VPS.

### Why should I use pnpm instead of npm on Hostinger?

Use pnpm on Hostinger to manage strict inode limits. Standard npm creates tens of thousands of individual files. pnpm uses a global content store with symlinks. This drastically cuts the real file count and prevents inode ceiling errors.

### How do I fix Hostinger inode limit issues with Next.js?

Add a `prebuild` script to your `package.json`: `"prebuild": "rm -rf .next/cache && rm -rf node_modules/.cache"`. Switch from npm to pnpm. These two steps keep your inode usage flat across multiple deployments.

### Is Cloudflare free to use with Hostinger?

Yes. Cloudflare's free plan works seamlessly with Hostinger. Point your domain's nameservers at Cloudflare. It proxies traffic to your server, providing free SSL, global CDN caching, and DDoS protection.

### How do I add AI search to a Next.js blog?

Use Cloudflare Workers AI to generate text embeddings for each post. Store them in Cloudflare Vectorize. When a user searches, embed the query and query Vectorize for similar chunks. Return the ranked results. No GPU server is required.

### Does Cloudflare Workers AI work without a Cloudflare Worker?

Yes. Cloudflare Workers AI offers a standard REST API. Any server that makes HTTPS requests can call it directly. A Next.js API route on Hostinger works perfectly.

### How do I auto-index blog posts to Cloudflare Vectorize on every publish?

Create a GitHub Actions workflow. Trigger it on `push` to `main` with paths restricted to `content/**/*.mdx`. Run your embedding script to generate vectors and upsert them to Vectorize. Use a content-hash cache file to skip unchanged posts. Commit the cache back with a `[skip ci]` tag.


---

<!-- METADATA_START -->
## Metadata & Citations

### Further Reading
- [Goodbye reCAPTCHA, Hello Turnstile: Why I Switched (Next.js Guide)](https://ranti.dev/blog/migrating-from-recaptcha-to-turnstile.md)
- [Running Local LLM Agents in Kubernetes: A Practitioner's Guide to vLLM on EKS](https://ranti.dev/blog/vllm-on-eks.md)
- [Kiro vs Cursor vs Windsurf vs Claude Code vs Codex vs Antigravity: What I Actually Use as an SRE](https://ranti.dev/blog/kiro-vs-cursor-vs-windsurf-vs-claude-vs-codex-vs-antigravity.md)

### Navigation
- [Back to Bio Hub](https://ranti.dev/.md)
- [Full Site Manifest](https://ranti.dev/llms.txt)

```json
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Next.js on Hostinger with GitHub, Cloudflare & AI Search",
  "author": {
    "@type": "Person",
    "name": "Rantideb Howlader"
  },
  "datePublished": "2026-07-20T00:00:00.000Z",
  "url": "https://ranti.dev/blog/hostinger-cloudflare-nextjs-hosting-guide",
  "license": "https://creativecommons.org/licenses/by/4.0/",
  "isAccessibleForFree": true
}
```

### BibTeX
```bibtex
@article{hostinger-cloudflare-nextjs-hosting-guide_2026,
  author = {Rantideb Howlader},
  title = {Next.js on Hostinger with GitHub, Cloudflare & AI Search},
  journal = {Rantideb Howlader Portfolio},
  year = {2026},
  url = {https://ranti.dev/blog/hostinger-cloudflare-nextjs-hosting-guide},
  note = {Accessed: 2026-09-21}
}
```

### IEEE
Rantideb Howlader, "Next.js on Hostinger with GitHub, Cloudflare & AI Search," Rantideb Howlader Portfolio, 2026. [Online]. Available: https://ranti.dev/blog/hostinger-cloudflare-nextjs-hosting-guide. [Accessed: 2026-09-21].

### APA
Rantideb Howlader. (2026). Next.js on Hostinger with GitHub, Cloudflare & AI Search. Rantideb Howlader. Retrieved from https://ranti.dev/blog/hostinger-cloudflare-nextjs-hosting-guide

--- 
*This content is provided in research-grade Markdown format. Required Attribution: Cite as Rantideb Howlader (2026).*
<!-- METADATA_END -->