DEV Community

William Wang
William Wang

Posted on

Building an AI-Powered Investment Knowledge Base with Nuxt 3 and Go-Zero

Investment wisdom is scattered across hundreds of books — from Benjamin Graham's The Intelligent Investor to Ray Dalio's Principles to Charlie Munger's speeches. As a developer and investor, I wanted to build a structured, searchable knowledge base that makes these principles accessible. Here's how I built KeepRule — an AI-powered investment knowledge base covering 26 legendary investors and 1,377 investing principles.

The Data Model

The core challenge was designing a schema that captures the relationships between investors, their philosophies, and individual rules. Here's the simplified structure:

-- Investors (Warren Buffett, Charlie Munger, etc.)
CREATE TABLE investors (
  id BIGINT PRIMARY KEY,
  name VARCHAR(128) NOT NULL,
  slug VARCHAR(128) NOT NULL,
  bio TEXT NOT NULL,
  lang VARCHAR(8) DEFAULT 'en'
);

-- Categories group related principles
CREATE TABLE categories (
  id BIGINT PRIMARY KEY,
  investor_id BIGINT NOT NULL,
  name VARCHAR(256) NOT NULL,
  sort_order INT DEFAULT 0
);

-- Individual investing rules/principles
CREATE TABLE rules (
  id BIGINT PRIMARY KEY,
  category_id BIGINT NOT NULL,
  title VARCHAR(512) NOT NULL,
  content TEXT NOT NULL,
  source VARCHAR(256) DEFAULT ''
);
Enter fullscreen mode Exit fullscreen mode

Each investor has categorized principles, tagged for cross-referencing. A single principle like "margin of safety" appears under Graham but links to Buffett and Munger through tags — because great ideas get borrowed.

Why Nuxt 3 SSR for a Content Site

For a content-heavy site targeting organic search traffic, SSR isn't optional — it's mandatory. Here's the Nuxt config that matters:

// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true,
  routeRules: {
    '/en/**': { swr: 3600 },
    '/zh/**': { swr: 3600 },
    '/api/**': { cors: true }
  },
  i18n: {
    locales: [
      { code: 'en', iso: 'en-US', dir: 'ltr' },
      { code: 'zh', iso: 'zh-CN', dir: 'ltr' }
    ],
    strategy: 'prefix',
    detectBrowserLanguage: false
  }
})
Enter fullscreen mode Exit fullscreen mode

Key decisions:

  • swr: 3600 — Stale-while-revalidate caching. Pages serve instantly from cache while Nuxt regenerates in the background. This alone cut TTFB from ~800ms to ~50ms for cached routes.
  • detectBrowserLanguage: false — Never redirect based on browser language. Search engines need stable URLs. Each language version lives at its own prefix (/en/, /zh/).
  • Prefix strategy for i18n — Every page has a clear language-specific URL that search engines can index independently.

Why Go-Zero Over Express or NestJS

The backend uses Go-Zero, a Go microservices framework. The decision came down to three things:

1. Code generation saves weeks. Define your API in a .api file and Go-Zero generates handlers, routes, types, and middleware:

// investorapi.api
type InvestorReq {
  Slug string `path:"slug"`
  Lang string `form:"lang,optional,default=en"`
}

type InvestorResp {
  Id       int64      `json:"id"`
  Name     string     `json:"name"`
  Bio      string     `json:"bio"`
  Rules    []RuleItem `json:"rules"`
}

service investorapi-api {
  @handler GetInvestor
  get /api/investor/:slug (InvestorReq) returns (InvestorResp)
}
Enter fullscreen mode Exit fullscreen mode

Run goctl api go -api investorapi.api -dir . and you get a fully wired handler with request validation.

2. Built-in caching. Go-Zero has native Redis caching at the ORM level. Database queries automatically cache and invalidate:

func (m *defaultInvestorModel) FindOneBySlug(ctx context.Context, slug string) (*Investor, error) {
  var resp Investor
  query := fmt.Sprintf("SELECT %s FROM %s WHERE slug = ? LIMIT 1", investorRows, m.table)
  err := m.QueryRowCtx(ctx, &resp, slug, func(conn sqlx.Conn, v interface{}) error {
    return conn.QueryRowCtx(ctx, v, query, slug)
  })
  return &resp, err
}
Enter fullscreen mode Exit fullscreen mode

3. Performance. Go handles concurrent requests far more efficiently than Node.js for CPU-bound work like rendering structured data responses. Our p99 API latency sits at ~12ms.

AI Chat Integration

Each investor page includes an AI chat feature — ask Warren Buffett about your portfolio, or debate market timing with Peter Lynch. The integration uses Claude's API with a carefully crafted system prompt:

const systemPrompt = `You are ${investor.name}, the legendary investor.
Answer questions using these verified principles:
${investor.rules.map(r => `- ${r.title}: ${r.content}`).join('\n')}
Stay in character. Cite specific principles when relevant.
If asked about something outside your expertise, say so honestly.`
Enter fullscreen mode Exit fullscreen mode

Grounding the AI in the actual extracted principles significantly reduces hallucination — the model sticks to what the real investor actually said.

The 30x Nginx Cache Trick

The single biggest performance win was adding Nginx proxy caching in front of the Nuxt SSR server:

proxy_cache_path /var/cache/nginx/buffett levels=1:2
  keys_zone=buffett_cache:50m max_size=1g inactive=60m;

server {
  location / {
    proxy_cache buffett_cache;
    proxy_cache_valid 200 30m;
    proxy_cache_use_stale error timeout updating;
    proxy_pass http://127.0.0.1:6001;
    add_header X-Cache-Status $upstream_cache_status;
  }
}
Enter fullscreen mode Exit fullscreen mode

Before: ~600ms TTFB (Nuxt SSR rendering). After: ~20ms TTFB (Nginx serving from disk cache). That's a 30x improvement for zero application code changes. The proxy_cache_use_stale directive means users never see a slow response, even during cache refresh.

SEO That Actually Worked

With 4,673 pages indexed across two languages, here's what moved the needle:

  • Structured data (JSON-LD) on every page — FAQ schema for Q&A pages, Person schema for investor profiles
  • hreflang tags linking English and Chinese versions of every page
  • Programmatic internal linking — each rule links to related rules via shared tags, creating a dense link graph
  • Auto-generated sitemap with proper lastmod dates and language alternates
  • Descriptive URLs like /en/investor/warren-buffett/margin-of-safety instead of /rule/1234

Results

  • 26 investors profiled with full principle breakdowns
  • 1,377 principles extracted, categorized, and tagged
  • 4,673 pages indexed by Google across EN and ZH
  • Sub-100ms TTFB on cached pages
  • AI chat grounded in real investor principles

Try It

If you're building a content-heavy site and debating between SPA and SSR — go SSR. The SEO benefits alone justify the added complexity. And if your backend is mostly CRUD with caching needs, give Go-Zero a serious look.

Happy to answer questions in the comments.

Top comments (0)