惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
J
Java Code Geeks
雷峰网
雷峰网
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
腾讯CDC
博客园 - 司徒正美
B
Blog RSS Feed
博客园 - 三生石上(FineUI控件)
I
InfoQ
N
Netflix TechBlog - Medium
L
LangChain Blog
博客园_首页
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Tailwind CSS Blog
MyScale Blog
MyScale Blog
美团技术团队
The Cloudflare Blog
爱范儿
爱范儿
Stack Overflow Blog
Stack Overflow Blog
博客园 - 聂微东
H
Help Net Security
Martin Fowler
Martin Fowler
V
Visual Studio Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I Built a Swedish Crossword Solver with Astro and 400...
Evy Lundel · 2026-05-09 · via DEV Community

How I Built a Swedish Crossword Solver with Astro and 400,000+ Words

I recently launched Korsordsakuten — a free Swedish crossword solver — and learned a lot about building SEO-driven content sites with Astro. Here's what I built and what I learned along the way.

What It Does

The site lets you:

  • Search 400,000+ Swedish word forms by clue, pattern, or length
  • Filter answers by letter count (e.g. "give me 6-letter synonyms only")
  • Browse prefix/suffix indexes (words starting with SK, ending with ERA etc.)
  • Solve anagrams
  • Play a daily Wordle-style word game

The Stack

  • Astro 6 (SSR mode, Node adapter) — perfect for content-heavy sites. Each route is server-rendered but the build is still fast.
  • Node.js 22 on Render (free tier)
  • GitHub Pages as a static sitemap mirror — more on this below
  • Zero client-side JS frameworks. Just vanilla JS where needed.

The Data Pipeline

The word database is built from public Swedish word lists, processed through a Node.js pipeline:

scripts/
  build-worddb.mjs      # Build words.json, synonyms.json, related.json
  build-phrases.mjs     # Process multi-word crossword entries
  build-sitemaps.mjs    # Generate 278 sitemap files × 1000 URLs each
  publish-sitemaps.mjs  # Push sitemaps to GitHub Pages mirror

Enter fullscreen mode Exit fullscreen mode

The synonym/related data is derived from Swedish lexical resources, giving each word a list of crossword-appropriate answers.

The Trickiest Part: Memory on a Free Tier

The site has some large JSON files:

  • clue-index.json — 18 MB
  • related.json — 8.5 MB
  • synonyms.json — 6.75 MB

Loading all of these at module startup caused OOM crashes on Render's 256 MB free tier. The fix: lazy loading via getter functions.

// Before — crashes on startup
import wordsData from '../data/words.json';

// After — only loads when first accessed
let _words: string[] | null = null;
export function getWords(): string[] {
  if (!_words) _words = wordsData as string[];
  return _words;
}

Enter fullscreen mode Exit fullscreen mode

Also added --max-old-space-size=460 to the start script (Render allows slightly over the nominal 256 MB before killing the process).

The Sitemap Problem

With 227,000+ URLs, Google wouldn't read our sitemaps. Two issues:

  1. Files were too large — 45,000 URLs × ~120 bytes = 5.4 MB per file. Google has an unofficial ~1 MB soft limit. Fixed by reducing to 1,000 URLs per file (278 files).

  2. The host went down — Render free tier sleeps on inactivity. When it's down, Google can't fetch the sitemap and backs off for weeks.

Solution: Push sitemaps to GitHub Pages as a static mirror. A simple script clones the repo, copies the XML files, rewrites the sitemap index URLs to point to the mirror, and pushes:

// publish-sitemaps.mjs (simplified)
const files = readdirSync(SRC_DIR).filter(f => /^sitemap.*\.xml$/.test(f));

for (const f of files) {
  if (f === 'sitemap.xml') {
    // Rewrite index so sub-sitemap URLs point to the mirror
    const content = readFileSync(join(SRC_DIR, f), 'utf-8');
    const rewritten = content.replace(
      /https:\/\/www\.korsordsakuten\.se\/(sitemap-\d+\.xml)/g,
      'https://sitemaps.korsordsakuten.se/$1'
    );
    writeFileSync(join(WORK_DIR, f), rewritten);
  } else {
    copyFileSync(join(SRC_DIR, f), join(WORK_DIR, f));
  }
}

Enter fullscreen mode Exit fullscreen mode

A custom subdomain (sitemaps.korsordsakuten.se → GitHub Pages via CNAME) makes the URLs clean. Now Google can always fetch sitemaps even if the main site is sleeping.

Structured Data That Actually Helps

For clue pages (/korsord/[word]), I added QAPage schema instead of just FAQPage:

{
  "@type": "QAPage",
  "mainEntity": {
    "@type": "Question",
    "name": "avslutningsvis — korsordssvar",
    "answerCount": 8,
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "SLUTLIGEN (9 bokstäver)",
      "upvoteCount": 8
    },
    "suggestedAnswer": []
  }
}

Enter fullscreen mode Exit fullscreen mode

This signals to Google that each clue page is structured Q&A content — similar to how Q&A forum sites are interpreted — rather than auto-generated thin content.

For word pages, DefinedTerm with synonyms as alternateName marks the page as a lexical resource:

{
  "@type": "DefinedTerm",
  "name": "PLATS",
  "alternateName": ["STÄLLE", "POSITION", "LÄGE"],
  "inDefinedTermSet": {
    "@type": "DefinedTermSet",
    "name": "Korsordsakuten ordlista"
  }
}

Enter fullscreen mode Exit fullscreen mode

Daily Fresh Content

One thing competitor sites have that purely static sites lack: freshness signals. Google re-crawls active sites more frequently.

Solution: /dagens-ledtradar — a page showing 24 curated crossword clues that rotates every day using a deterministic seed:

const dayNum = Math.floor((Date.now() - epoch) / 86400000);
const rng = mulberry32(dayNum * 7919 + 13);
// Pick 24 unique entries from top-2000 clues

Enter fullscreen mode Exit fullscreen mode

Same date = same picks (the page is cacheable), but crawling the URL the next day returns different content. Triggers Google's freshness heuristic without any database or cron job.

What I'd Do Differently

  • Start with a paid host. Render free tier sleeping + OOM issues cost weeks of SEO recovery time.
  • Plan for JSON size early. Lazy loading was the fix but the problem was predictable.
  • Submit 10 hand-picked URLs to GSC from day one. Don't wait for the sitemap crawler to discover everything.

Links

Happy to answer questions about any part of the stack!