feat: add /compare and /guides/quick-start SEO pages
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m51s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m51s
- Compare page: SnapAPI vs ScreenshotOne, URLBox, ApiFlash, CaptureKit, GetScreenshot - Quick-start guide: 5-step developer tutorial with cURL, GET, SDK examples - Both pages: dark theme, JSON-LD, OG tags, canonical URLs, mobile responsive - Added clean URL redirects in routing - Updated sitemap.xml and index.html nav - Added seo-pages.test.ts (10 tests, all passing)
This commit is contained in:
parent
e9ee3a6c2c
commit
9d1170fb9a
6 changed files with 547 additions and 0 deletions
|
|
@ -135,6 +135,10 @@ for (const page of ["social-media-previews", "website-monitoring", "pdf-reports"
|
|||
app.get(`/use-cases/${page}`, (_req, res) => res.redirect(301, `/use-cases/${page}.html`));
|
||||
}
|
||||
|
||||
// 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"));
|
||||
|
||||
// Static files (landing page)
|
||||
app.use(express.static(path.join(__dirname, "../public"), { etag: true }));
|
||||
|
||||
|
|
|
|||
107
src/routes/__tests__/seo-pages.test.ts
Normal file
107
src/routes/__tests__/seo-pages.test.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import request from 'supertest'
|
||||
import express from 'express'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import fs from 'fs'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const publicDir = path.join(__dirname, '../../../public')
|
||||
|
||||
function createApp() {
|
||||
const app = express()
|
||||
|
||||
// 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.use(express.static(publicDir, { etag: true }))
|
||||
return app
|
||||
}
|
||||
|
||||
describe('Compare Page', () => {
|
||||
const app = createApp()
|
||||
|
||||
it('GET /compare.html returns 200', async () => {
|
||||
const res = await request(app).get('/compare.html')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers['content-type']).toContain('text/html')
|
||||
})
|
||||
|
||||
it('GET /compare redirects 301 to .html', async () => {
|
||||
const res = await request(app).get('/compare')
|
||||
expect(res.status).toBe(301)
|
||||
expect(res.headers.location).toBe('/compare.html')
|
||||
})
|
||||
|
||||
it('contains required SEO elements', async () => {
|
||||
const res = await request(app).get('/compare.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"/)
|
||||
expect(html).toMatch(/<meta name="twitter:card"/)
|
||||
expect(html).toContain('"@type":"WebPage"')
|
||||
})
|
||||
|
||||
it('mentions competitors factually', async () => {
|
||||
const res = await request(app).get('/compare.html')
|
||||
const html = res.text
|
||||
for (const competitor of ['ScreenshotOne', 'URLBox', 'ApiFlash', 'CaptureKit', 'GetScreenshot']) {
|
||||
expect(html).toContain(competitor)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Quick-Start Guide', () => {
|
||||
const app = createApp()
|
||||
|
||||
it('GET /guides/quick-start.html returns 200', async () => {
|
||||
const res = await request(app).get('/guides/quick-start.html')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers['content-type']).toContain('text/html')
|
||||
})
|
||||
|
||||
it('GET /guides/quick-start redirects 301 to .html', async () => {
|
||||
const res = await request(app).get('/guides/quick-start')
|
||||
expect(res.status).toBe(301)
|
||||
expect(res.headers.location).toBe('/guides/quick-start.html')
|
||||
})
|
||||
|
||||
it('contains required SEO elements', async () => {
|
||||
const res = await request(app).get('/guides/quick-start.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"/)
|
||||
expect(html).toMatch(/<meta name="twitter:card"/)
|
||||
expect(html).toContain('"@type":"HowTo"')
|
||||
})
|
||||
|
||||
it('contains step-by-step content', async () => {
|
||||
const res = await request(app).get('/guides/quick-start.html')
|
||||
const html = res.text
|
||||
expect(html).toContain('curl')
|
||||
expect(html).toContain('API key')
|
||||
expect(html).toContain('GET')
|
||||
})
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue