This page structure reference is a map of the site's information architecture, kept as a normal content page so it shows up in the admin console like any other page and stays easy to find. For the always-current list of live URLs, see the Site Map or the XML sitemap.xml: this page structure document describes the shape of the site, not a generated snapshot.
Page Structure: Static Pages
The static-pages layer of this page structure lists pages with their own route file, not backed by an MDX content list: Home, About, About/Founder, About/Team, Services, Growth Audit, Blog (hub), Case Studies (hub), Success Stories (hub), Tutorials (hub), Events (hub), Careers (hub), Contact, Site Map, Policies, Privacy, Terms, Cookie Policy, Refund Policy. These are tracked in lib/staticRoutes.ts so the XML sitemap and the human-readable site map can't drift apart.
Content-driven pages
This part of the page structure covers everything that's a git-tracked MDX file under content/, loaded by a lib/*.ts reader and rendered through a shared template:
-
Landing pages (
content/pages/): services (34, underdigital-marketing-services/), industries (12, underindustry/), resources (16, underresources/), countries (8, underdigital-marketing-services/{usa,uk,canada,australia,singapore,uae,new-zealand,ireland}.mdx, hub atcountries.mdx), plus a handful of top-level pages. All served by theapp/(site)/[...slug]/page.tsxcatch-all, which classifies each page'sLandingPageType(service, industry, country, etc.) vialib/pages.tsto decide hero/band treatment. -
Blog (
content/blog/, ~330 posts): categories and tags normalized throughlib/taxonomy.ts. Cover and in-article images are generated externally (ChatGPT/DALL-E) and uploaded through the admin image-prompts tool, described under Blog images below. -
Case studies (
content/case-studies/): hascategory/tagsplusindustry. -
Success stories (
content/success-stories/): hasproducts/solutionsplusindustry. -
Tutorials (
content/tutorials/): hascategory/tags. -
Careers (
content/careers/): usesdepartmentas its category-equivalent, plustags. -
Events (
content/events/): categories fromlib/taxonomy.ts, venues/organizers resolved vialib/eventEntities.ts. -
Team (
content/team/): profile registry powering/about/teamand author bylines; seelib/team.ts.
Taxonomies
The taxonomy layer of this page structure covers categories, tags, industries, products/solutions, event venues/organizers, and the country list, all plain git-tracked config, not database tables: lib/taxonomy.ts, lib/eventEntities.ts, lib/countries.ts. Adding a new category means editing that file and committing, the same effort as any other code change. Tags stay free-form (only case-normalized) since the volume of real tag values is too high for a fixed list.
Database (Postgres, via Drizzle)
The database layer of this page structure only backs data that's genuinely dynamic at runtime; content/ stays the source of truth for anything editorial:
-
leads: every contact/growth-audit/whitepaper-download/job-application submission, with CRM status fields for follow-up. -
keywordRankings: historical rank-tracking snapshots for the SEO dashboard. -
linkCheckRuns/linkCheckResults: broken-link checker run history (internal links checked synchronously, external links trickle in via a daily cron given the Vercel Hobby-tier limit). -
adminUsers: role assignments (owner/seo/sales) for admin RBAC. TheADMIN_EMAILSenv allowlist is a permanent break-glass path layered on top, so a bad row here can never lock the site owner out. -
redirects: old-URL → new-URL 301/307 mappings, checked inproxy.tsfor every request. Built while migrating off the old WordPress site so URL-structure changes (dropping the/category/segment, etc.) don't lose SEO equity. -
blogImages: tracks which cover/content images (stored on Cloudflare R2, not in Postgres) have been uploaded per blog slug, so the admin UI can show upload status without re-checking R2 on every load.
Blog images (Cloudflare R2)
This part of the page structure covers blog images: each blog post can have a cover image plus two in-article images. The admin image-prompts tool (/admin/blogs/[slug]/image-prompts) generates a ChatGPT/DALL-E prompt per image tuned to that post's category and topic (lib/imagePrompts.ts), then lets you upload the generated file: it's converted to WebP (sharp) and stored on Cloudflare R2 (lib/r2.ts), with the URL recorded in blogImages.
Uploading the cover image from a writable filesystem (i.e. running the admin locally) writes coverImage straight into the post's .mdx frontmatter automatically; content images always need the given markdown snippet pasted into the post body by hand, since only a person can judge where in the article an image belongs. The /admin/blogs list shows an Images column (N/3) so progress across all posts is visible at a glance.
Admin console
The admin console section of this page structure is gated by Google OAuth (auth.ts) against the ADMIN_EMAILS allowlist, with role-based section visibility (owner/seo/sales) layered on top via lib/rbac/permissions.ts. Current sections, grouped as they appear in the sidebar:
- SEO: SEO Dashboard, Blogs (with per-post image-prompts and SEO audit sub-pages)
- Sales: Leads (CRM)
- Technical: Landing Pages, URL Export, Link Checker, Redirects, Team & Access, Settings
/api/admin/* routes aren't covered by the proxy.ts middleware matcher, so every admin-mutating API route (blog image upload, etc.) does its own auth() check inside the handler, the one deliberate exception to how the rest of this page structure is guarded.