Ahrefs 96->100

This commit is contained in:
Timo Knuth 2026-01-15 10:14:40 +01:00
parent 0a02876ea4
commit 83ea141230
17 changed files with 694 additions and 579 deletions

View File

@ -0,0 +1 @@
bb6dfaacf1ed41a880281c426c54ed7c

View File

@ -4,7 +4,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
const HOST = 'www.qrmaster.net'; const HOST = 'www.qrmaster.net';
const KEY = '1234567890abcdef'; const KEY = 'bb6dfaacf1ed41a880281c426c54ed7c';
const KEY_LOCATION = `https://${HOST}/${KEY}.txt`; const KEY_LOCATION = `https://${HOST}/${KEY}.txt`;
const INDEXNOW_ENDPOINT = 'https://api.indexnow.org/indexnow'; const INDEXNOW_ENDPOINT = 'https://api.indexnow.org/indexnow';

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,14 @@ import AppLayout from './AppLayout';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Dashboard | QR Master', title: 'Dashboard | QR Master',
description: 'Manage your QR Master dashboard. Create dynamic QR codes, view real-time scan analytics, and configure your account settings in one secure place.', description: 'Manage your QR Master dashboard. Create dynamic QR codes, view real-time scan analytics, and configure your account settings in one secure place.',
robots: { index: false, follow: false }, // Dashboard pages shouldn't be indexed generally robots: { index: false, follow: false },
icons: {
icon: [
{ url: '/favicon.svg', type: 'image/svg+xml' },
{ url: '/logo.svg', type: 'image/svg+xml' },
],
apple: '/logo.svg',
},
}; };
export default function RootAppLayout({ export default function RootAppLayout({

View File

@ -5,7 +5,13 @@ import type { Metadata } from 'next';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Authentication | QR Master', title: 'Authentication | QR Master',
description: 'Securely login or sign up to QR Master to manage your dynamic QR codes, track analytics, and access premium features. Your gateway to professional QR management.', description: 'Securely login or sign up to QR Master to manage your dynamic QR codes, track analytics, and access premium features. Your gateway to professional QR management.',
icons: {
icon: [
{ url: '/favicon.svg', type: 'image/svg+xml' },
{ url: '/logo.svg', type: 'image/svg+xml' },
],
apple: '/logo.svg',
},
}; };
export default function AuthRootLayout({ export default function AuthRootLayout({

View File

@ -11,6 +11,24 @@ export const metadata: Metadata = {
alternates: { alternates: {
canonical: 'https://www.qrmaster.net/login', canonical: 'https://www.qrmaster.net/login',
}, },
openGraph: {
title: 'Login to QR Master | Access Your Dashboard',
description: 'Sign in to QR Master to create, manage, and track your QR codes.',
url: 'https://www.qrmaster.net/login',
type: 'website',
images: [{
url: 'https://www.qrmaster.net/og-image.png',
width: 1200,
height: 630,
alt: 'QR Master Login',
}],
},
twitter: {
card: 'summary_large_image',
title: 'Login to QR Master | Access Your Dashboard',
description: 'Sign in to QR Master to create, manage, and track your QR codes.',
images: ['https://www.qrmaster.net/og-image.png'],
},
}; };

View File

@ -11,6 +11,24 @@ export const metadata: Metadata = {
alternates: { alternates: {
canonical: 'https://www.qrmaster.net/signup', canonical: 'https://www.qrmaster.net/signup',
}, },
openGraph: {
title: 'Create Free Account | QR Master',
description: 'Sign up for QR Master to create free QR codes with tracking and customization.',
url: 'https://www.qrmaster.net/signup',
type: 'website',
images: [{
url: 'https://www.qrmaster.net/og-image.png',
width: 1200,
height: 630,
alt: 'QR Master Sign Up',
}],
},
twitter: {
card: 'summary_large_image',
title: 'Create Free Account | QR Master',
description: 'Sign up for QR Master to create free QR codes with tracking and customization.',
images: ['https://www.qrmaster.net/og-image.png'],
},
}; };

View File

@ -0,0 +1,22 @@
'use client';
import React from 'react';
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
export function ContactSupport() {
return (
<div className="mt-16 bg-blue-50 border-l-4 border-blue-500 p-8 rounded-r-lg">
<h2 className="text-2xl font-bold mb-4 text-gray-900">
Still have questions?
</h2>
<p className="text-lg text-gray-700 mb-6 leading-relaxed">
Our support team is here to help. Contact us at{' '}
<ObfuscatedMailto
email="support@qrmaster.net"
className="text-blue-600 hover:text-blue-700 font-semibold"
/>{' '}
or reach out through our live chat.
</p>
</div>
);
}

View File

@ -3,6 +3,7 @@ import type { Metadata } from 'next';
import SeoJsonLd from '@/components/SeoJsonLd'; import SeoJsonLd from '@/components/SeoJsonLd';
import { faqPageSchema } from '@/lib/schema'; import { faqPageSchema } from '@/lib/schema';
import { Card, CardContent } from '@/components/ui/Card'; import { Card, CardContent } from '@/components/ui/Card';
import { ContactSupport } from './ContactSupport';
function truncateAtWord(text: string, maxLength: number): string { function truncateAtWord(text: string, maxLength: number): string {
if (text.length <= maxLength) return text; if (text.length <= maxLength) return text;
@ -131,18 +132,7 @@ export default function FAQPage() {
))} ))}
</div> </div>
<div className="mt-16 bg-blue-50 border-l-4 border-blue-500 p-8 rounded-r-lg"> <ContactSupport />
<h2 className="text-2xl font-bold mb-4 text-gray-900">
Still have questions?
</h2>
<p className="text-lg text-gray-700 mb-6 leading-relaxed">
Our support team is here to help. Contact us at{' '}
<a href="mailto:support@qrmaster.net" className="text-blue-600 hover:text-blue-700 font-semibold">
support@qrmaster.net
</a>{' '}
or reach out through our live chat.
</p>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,6 +7,7 @@ import { Badge } from '@/components/ui/Badge';
import { showToast } from '@/components/ui/Toast'; import { showToast } from '@/components/ui/Toast';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { BillingToggle } from '@/components/ui/BillingToggle'; import { BillingToggle } from '@/components/ui/BillingToggle';
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
export default function PricingClient() { export default function PricingClient() {
const router = useRouter(); const router = useRouter();
@ -182,9 +183,9 @@ export default function PricingClient() {
return ( return (
<div className="container mx-auto px-4 py-12"> <div className="container mx-auto px-4 py-12">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-4xl font-bold text-gray-900 mb-4">
Choose Your Plan Choose Your Plan
</h1> </h2>
<p className="text-xl text-gray-600"> <p className="text-xl text-gray-600">
Select the perfect plan for your QR code needs Select the perfect plan for your QR code needs
</p> </p>
@ -260,7 +261,7 @@ export default function PricingClient() {
All plans include unlimited static QR codes and basic customization. All plans include unlimited static QR codes and basic customization.
</p> </p>
<p className="text-gray-600 mt-2"> <p className="text-gray-600 mt-2">
Need help choosing? <a href="mailto:support@qrmaster.net" className="text-primary-600 hover:text-primary-700 underline">Contact our team</a> Need help choosing? <ObfuscatedMailto email="support@qrmaster.net" className="text-primary-600 hover:text-primary-700 underline">Contact our team</ObfuscatedMailto>
</p> </p>
</div> </div>
</div> </div>

View File

@ -0,0 +1,13 @@
'use client';
import React from 'react';
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
export function PrivacyEmailLink() {
return (
<ObfuscatedMailto
email="support@qrmaster.net"
className="text-primary-600 hover:text-primary-700"
/>
);
}

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { PrivacyEmailLink } from './PrivacyEmailLink';
export const metadata = { export const metadata = {
title: 'Privacy Policy | QR Master', title: 'Privacy Policy | QR Master',
@ -110,9 +111,7 @@ export default function PrivacyPage() {
</ul> </ul>
<p className="text-gray-700 mb-4"> <p className="text-gray-700 mb-4">
To exercise these rights, contact us at{' '} To exercise these rights, contact us at{' '}
<a href="mailto:support@qrmaster.net" className="text-primary-600 hover:text-primary-700"> <PrivacyEmailLink />
support@qrmaster.net
</a>
</p> </p>
<p className="text-gray-700 mb-4"> <p className="text-gray-700 mb-4">
Our service is for users 16 years and older. If you're in the EEA and have concerns, Our service is for users 16 years and older. If you're in the EEA and have concerns,
@ -128,9 +127,7 @@ export default function PrivacyPage() {
<div className="bg-gray-50 p-6 rounded-lg"> <div className="bg-gray-50 p-6 rounded-lg">
<p className="text-gray-700 mb-2"> <p className="text-gray-700 mb-2">
<strong>Email:</strong>{' '} <strong>Email:</strong>{' '}
<a href="mailto:support@qrmaster.net" className="text-primary-600 hover:text-primary-700"> <PrivacyEmailLink />
support@qrmaster.net
</a>
</p> </p>
<p className="text-gray-700 mb-2"><strong>Website:</strong> <a href="/" className="text-primary-600 hover:text-primary-700">qrmaster.net</a></p> <p className="text-gray-700 mb-2"><strong>Website:</strong> <a href="/" className="text-primary-600 hover:text-primary-700">qrmaster.net</a></p>
</div> </div>

View File

@ -4,6 +4,13 @@ import '@/styles/globals.css';
export const metadata = { export const metadata = {
title: 'vCard Download', title: 'vCard Download',
description: 'Download contact information', description: 'Download contact information',
icons: {
icon: [
{ url: '/favicon.svg', type: 'image/svg+xml' },
{ url: '/logo.svg', type: 'image/svg+xml' },
],
apple: '/logo.svg',
},
}; };
export default function VCardLayout({ export default function VCardLayout({

View File

@ -10,10 +10,10 @@ export default function SeoJsonLd({ data }: SeoJsonLdProps) {
return ( return (
<> <>
{jsonLdArray.map((item, index) => { {jsonLdArray.map((item, index) => {
const schema = { // Only add @context if it doesn't already exist in the item
'@context': 'https://schema.org', const schema = (item as any)['@context']
...item, ? item
}; : { '@context': 'https://schema.org', ...item };
return ( return (
<script <script

View File

@ -66,9 +66,9 @@ export const Hero: React.FC<HeroProps> = ({ t }) => {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="space-y-6" className="space-y-6"
> >
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight"> <h2 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
{t.hero.title} {t.hero.title}
</h1> </h2>
<p className="text-xl text-gray-600 leading-relaxed max-w-2xl"> <p className="text-xl text-gray-600 leading-relaxed max-w-2xl">
{t.hero.subtitle} {t.hero.subtitle}

View File

@ -0,0 +1,34 @@
'use client';
import React, { useState, useEffect } from 'react';
interface ObfuscatedMailtoProps {
email: string;
className?: string;
children?: React.ReactNode;
}
/**
* Renders an email link that only becomes a clickable mailto: link after client-side hydration.
* This prevents Cloudflare's Email Obfuscation from breaking the link in the static HTML,
* which causes crawlers like Ahrefs to report 404 errors on /cdn-cgi/l/email-protection.
*/
export function ObfuscatedMailto({ email, className, children }: ObfuscatedMailtoProps) {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
// Before hydration, render as plain text/span to avoid Cloudflare manipulation
if (!isMounted) {
return <span className={className}>{children || email}</span>;
}
// After hydration, render as a proper mailto link
return (
<a href={`mailto:${email}`} className={className}>
{children || email}
</a>
);
}

View File

@ -21,7 +21,7 @@ export const blogPosts: Record<string, BlogPostData> = {
'qr-code-analytics': { 'qr-code-analytics': {
slug: 'qr-code-analytics', slug: 'qr-code-analytics',
title: 'QR Code Analytics: The Complete Guide', title: 'QR Code Analytics: The Complete Guide',
excerpt: 'Master QR Code Analytics with our complete guide. Learn how to track scans, measure ROI, and optimize your marketing campaigns using real-time data and insights.', excerpt: 'Master QR Code Analytics with our complete guide. Learn how to track scans, measure ROI, and optimize your marketing campaigns using real-time data.',
date: 'October 16, 2025', date: 'October 16, 2025',
datePublished: '2025-10-16T09:00:00Z', datePublished: '2025-10-16T09:00:00Z',
dateModified: '2025-10-16T09:00:00Z', dateModified: '2025-10-16T09:00:00Z',
@ -142,7 +142,7 @@ export const blogPosts: Record<string, BlogPostData> = {
'qr-code-tracking-guide-2025': { 'qr-code-tracking-guide-2025': {
slug: 'qr-code-tracking-guide-2025', slug: 'qr-code-tracking-guide-2025',
title: 'QR Code Tracking: Complete Guide 2025', title: 'QR Code Tracking: Complete Guide 2025',
excerpt: 'The complete guide to QR Code Tracking in 2025. Learn how to track scans, measure ROI with analytics tools, and optimize your marketing campaigns for maximum engagement.', excerpt: 'The complete guide to QR Code Tracking in 2025. Learn how to track scans, measure ROI, and optimize your marketing campaigns.',
date: 'October 18, 2025', date: 'October 18, 2025',
datePublished: '2025-10-18T09:00:00Z', datePublished: '2025-10-18T09:00:00Z',
dateModified: '2025-10-18T09:00:00Z', dateModified: '2025-10-18T09:00:00Z',
@ -668,7 +668,7 @@ app.get('/qr/:id', async (req, res) => {
'dynamic-vs-static-qr-codes': { 'dynamic-vs-static-qr-codes': {
slug: 'dynamic-vs-static-qr-codes', slug: 'dynamic-vs-static-qr-codes',
title: 'Dynamic vs Static QR Codes: The Ultimate Comparison', title: 'Dynamic vs Static QR Codes: The Ultimate Comparison',
excerpt: 'Static vs Dynamic QR Codes: Which one should you choose? Learn the key differences, pros and cons, and why dynamic QR codes are the better choice for business and marketing.', excerpt: 'Static vs Dynamic QR Codes: Which should you choose? Learn the key differences, pros and cons, and why dynamic codes are better for business.',
date: 'October 17, 2025', date: 'October 17, 2025',
datePublished: '2025-10-17T09:00:00Z', datePublished: '2025-10-17T09:00:00Z',
dateModified: '2025-10-17T09:00:00Z', dateModified: '2025-10-17T09:00:00Z',