feat: add /pricing and /changelog SEO pages
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 10m43s

- Pricing page with full comparison table, feature matrix, FAQ, JSON-LD Product schema
- Changelog page with all versions v0.1.0-v0.6.0, JSON-LD Blog schema
- 301 redirects for clean URLs
- Added to sitemap.xml
- Pricing in main nav, changelog in footer
- 14 new tests (171 total)
This commit is contained in:
OpenClaw 2026-03-02 15:06:41 +01:00
parent 9d1170fb9a
commit 9609501d7b
6 changed files with 652 additions and 1 deletions

View file

@ -138,6 +138,8 @@ for (const page of ["social-media-previews", "website-monitoring", "pdf-reports"
// Clean URLs for SEO pages
app.get("/compare", (_req, res) => res.redirect(301, "/compare.html"));
app.get("/guides/quick-start", (_req, res) => res.redirect(301, "/guides/quick-start.html"));
app.get("/pricing", (_req, res) => res.redirect(301, "/pricing.html"));
app.get("/changelog", (_req, res) => res.redirect(301, "/changelog.html"));
// Static files (landing page)
app.use(express.static(path.join(__dirname, "../public"), { etag: true }));

View file

@ -14,6 +14,8 @@ function createApp() {
// Clean URL redirects matching index.ts
app.get('/compare', (_req, res) => res.redirect(301, '/compare.html'))
app.get('/guides/quick-start', (_req, res) => res.redirect(301, '/guides/quick-start.html'))
app.get('/pricing', (_req, res) => res.redirect(301, '/pricing.html'))
app.get('/changelog', (_req, res) => res.redirect(301, '/changelog.html'))
app.use(express.static(publicDir, { etag: true }))
return app
@ -92,16 +94,146 @@ describe('Quick-Start Guide', () => {
})
})
describe('Pricing Page', () => {
const app = createApp()
it('GET /pricing.html returns 200', async () => {
const res = await request(app).get('/pricing.html')
expect(res.status).toBe(200)
expect(res.headers['content-type']).toContain('text/html')
})
it('GET /pricing redirects 301 to .html', async () => {
const res = await request(app).get('/pricing')
expect(res.status).toBe(301)
expect(res.headers.location).toBe('/pricing.html')
})
it('contains required SEO elements', async () => {
const res = await request(app).get('/pricing.html')
const html = res.text
expect(html).toMatch(/<title>.+<\/title>/)
expect(html).toMatch(/<meta name="description" content=".+"/)
expect(html).toMatch(/<meta property="og:title"/)
expect(html).toMatch(/<meta property="og:description"/)
expect(html).toMatch(/<meta property="og:type" content="website"/)
expect(html).toMatch(/<link rel="canonical" href="https:\/\/snapapi\.eu\/pricing"/)
expect(html).toMatch(/<meta name="twitter:card"/)
})
it('contains JSON-LD Product schema with offers', async () => {
const res = await request(app).get('/pricing.html')
const html = res.text
expect(html).toContain('"@type":"Product"')
expect(html).toContain('"Offer"')
expect(html).toContain('"9.00"')
expect(html).toContain('"29.00"')
expect(html).toContain('"79.00"')
})
it('contains all three pricing tiers', async () => {
const res = await request(app).get('/pricing.html')
const html = res.text
expect(html).toContain('Starter')
expect(html).toContain('Pro')
expect(html).toContain('Business')
expect(html).toContain('€9')
expect(html).toContain('€29')
expect(html).toContain('€79')
})
it('contains feature comparison matrix', async () => {
const res = await request(app).get('/pricing.html')
const html = res.text
expect(html).toContain('PNG')
expect(html).toContain('Full-page')
expect(html).toContain('Custom viewport')
})
it('contains FAQ section', async () => {
const res = await request(app).get('/pricing.html')
const html = res.text
expect(html).toContain('FAQ')
expect(html).toContain('billing')
})
it('contains checkout CTA buttons', async () => {
const res = await request(app).get('/pricing.html')
const html = res.text
expect(html).toContain("checkout('starter')")
expect(html).toContain("checkout('pro')")
expect(html).toContain("checkout('business')")
})
})
describe('Changelog Page', () => {
const app = createApp()
it('GET /changelog.html returns 200', async () => {
const res = await request(app).get('/changelog.html')
expect(res.status).toBe(200)
expect(res.headers['content-type']).toContain('text/html')
})
it('GET /changelog redirects 301 to .html', async () => {
const res = await request(app).get('/changelog')
expect(res.status).toBe(301)
expect(res.headers.location).toBe('/changelog.html')
})
it('contains required SEO elements', async () => {
const res = await request(app).get('/changelog.html')
const html = res.text
expect(html).toMatch(/<title>.+<\/title>/)
expect(html).toMatch(/<meta name="description" content=".+"/)
expect(html).toMatch(/<meta property="og:title"/)
expect(html).toMatch(/<meta property="og:description"/)
expect(html).toMatch(/<link rel="canonical" href="https:\/\/snapapi\.eu\/changelog"/)
expect(html).toMatch(/<meta name="twitter:card"/)
})
it('contains JSON-LD Blog schema', async () => {
const res = await request(app).get('/changelog.html')
const html = res.text
expect(html).toContain('"@type":"Blog"')
})
it('contains all version entries in order', async () => {
const res = await request(app).get('/changelog.html')
const html = res.text
const v06 = html.indexOf('v0.6.0')
const v05 = html.indexOf('v0.5.0')
const v04 = html.indexOf('v0.4.0')
const v03 = html.indexOf('v0.3.0')
const v02 = html.indexOf('v0.2.0')
const v01 = html.indexOf('v0.1.0')
expect(v06).toBeGreaterThan(-1)
expect(v05).toBeGreaterThan(v06)
expect(v04).toBeGreaterThan(v05)
expect(v03).toBeGreaterThan(v04)
expect(v02).toBeGreaterThan(v03)
expect(v01).toBeGreaterThan(v02)
})
})
describe('Sitemap & Index updates', () => {
it('sitemap contains new URLs', () => {
const sitemap = fs.readFileSync(path.join(publicDir, 'sitemap.xml'), 'utf-8')
expect(sitemap).toContain('https://snapapi.eu/compare')
expect(sitemap).toContain('https://snapapi.eu/guides/quick-start')
expect(sitemap).toContain('https://snapapi.eu/pricing')
expect(sitemap).toContain('https://snapapi.eu/changelog')
})
it('index.html has links to new pages', () => {
const index = fs.readFileSync(path.join(publicDir, 'index.html'), 'utf-8')
expect(index).toContain('/compare')
expect(index).toContain('/guides/quick-start')
expect(index).toContain('/pricing')
})
it('footer has changelog link', () => {
const index = fs.readFileSync(path.join(publicDir, 'index.html'), 'utf-8')
expect(index).toContain('/changelog')
})
})