Skip to content

Commit

Permalink
feat: adds proper seo handling
Browse files Browse the repository at this point in the history
This commit brings the following changes:
- Adds proper seo handling
- Adds pink & salmon custom color palettes to tailwind.
- Adds construct metadata utility function.
- Enables react strict mode and disables eslint & typescript checks in
  next config.
- Adds favicon, thumbnail images.
  • Loading branch information
ixahmedxi committed May 17, 2024
1 parent 4433921 commit 9d0420f
Show file tree
Hide file tree
Showing 20 changed files with 304 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

- [x] Playwright E2E tests.
- [x] github actions.
- [ ] SEO (dub.co is a great reference) for example robots.ts file.
- [x] SEO (dub.co is a great reference) for example robots.ts file.
- [ ] Shadcn UI.
- [ ] Slate/Plate.js editor.
- [ ] tRPC api with tanstack query.
Expand Down
1 change: 1 addition & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ignorePaths:
- drizzle
- '**/*.svg'
- .vscode/extensions.json
- public
words:
- clsx
- commitlint
Expand Down
12 changes: 11 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ const jiti = createJiti(fileURLToPath(import.meta.url));
jiti('./src/env');

/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
reactStrictMode: true,

// We run ESLint and TypeScript separately in the CI pipeline
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
};

export default nextConfig;
Empty file removed public/.gitkeep
Empty file.
Binary file added public/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions public/browserconfig.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#111111</TileColor>
</tile>
</msapplication>
</browserconfig>
Binary file added public/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mstile-150x150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/thumbnail.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,64 @@
@import '@radix-ui/colors/gray.css';
@import '@radix-ui/colors/gray-dark.css';

:root,
.light {
--pink-1: #fffcfd;
--pink-2: #fef7f8;
--pink-3: #ffe9ee;
--pink-4: #ffdce4;
--pink-5: #fbced8;
--pink-6: #f4bfcc;
--pink-7: #ebacbc;
--pink-8: #e294a8;
--pink-9: #fe5591;
--pink-10: #f04886;
--pink-11: #c92268;
--pink-12: #631733;

--salmon-1: #fefcfc;
--salmon-2: #fff7f6;
--salmon-3: #ffebe9;
--salmon-4: #ffd9d5;
--salmon-5: #ffc9c4;
--salmon-6: #ffbbb6;
--salmon-7: #f4a8a3;
--salmon-8: #ea8f8a;
--salmon-9: #f86c6a;
--salmon-10: #ec5f5e;
--salmon-11: #cc4446;
--salmon-12: #582b29;
}

:root,
.dark {
--pink-1: #170e10;
--pink-2: #201316;
--pink-3: #3a1320;
--pink-4: #510d27;
--pink-5: #601431;
--pink-6: #71213d;
--pink-7: #8b304f;
--pink-8: #b53e67;
--pink-9: #fe5591;
--pink-10: #f04886;
--pink-11: #ff8ab0;
--pink-12: #ffd2dd;

--salmon-1: #160f0e;
--salmon-2: #1f1312;
--salmon-3: #3a1313;
--salmon-4: #501113;
--salmon-5: #60181a;
--salmon-6: #722526;
--salmon-7: #8b3535;
--salmon-8: #b44746;
--salmon-9: #f86c6a;
--salmon-10: #eb605f;
--salmon-11: #ff8e89;
--salmon-12: #ffd2cf;
}

@tailwind base;
@tailwind components;
@tailwind utilities;
Expand Down
11 changes: 3 additions & 8 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@ import { ThemeProvider } from 'next-themes';

import type { PropsWithChildren } from 'react';

export const metadata: Metadata = {
title: {
default: 'Noodle - Rethinking Student Productivity',
template: '%s - Noodle',
},
description:
'Noodle is a productivity platform including many tools students need to be productive and stay on top of their work such as note taking, task management, and more.',
};
import { constructMetadata } from '@/lib/utils';

export const metadata: Metadata = constructMetadata();

/**
* The root layout component of the application.
Expand Down
35 changes: 35 additions & 0 deletions src/app/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { MetadataRoute } from 'next';

import { constants } from '@/constants';

/**
* This function returns an object that represents the manifest.json file which
* next.js uses to create the manifest.json file.
*
* @returns The manifest.json file configuration.
*/
export default function manifest(): MetadataRoute.Manifest {
return {
name: constants.name,
short_name: constants.shortName,
description: constants.description,
start_url: '/',
display: 'standalone',
background_color: '#111111',
theme_color: '#F86C6A',
icons: [
{
src: '/android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'maskable',
},
{
src: '/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
};
}
19 changes: 19 additions & 0 deletions src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { MetadataRoute } from 'next';

import { getBaseUrl } from '@/lib/utils';

/**
* This function returns an object that represents the robots.txt file which
* next.js uses to create the robots.txt file.
*
* @returns The robots.txt file configuration.
*/
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: `${getBaseUrl()}/sitemap.xml`,
};
}
8 changes: 8 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const constants = {
name: 'Noodle - Rethinking Student Productivity',
shortName: 'Noodle',
tagline: 'Rethinking Student Productivity',
description:
'Noodle is a productivity platform including many tools students need to be productive and stay on top of their work such as note taking, task management, and more.',
twitter_handle: '@noodle_run',
};
43 changes: 41 additions & 2 deletions src/lib/utils.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,50 @@
import { describe, expect, it } from 'bun:test';
import { afterEach, describe, expect, it } from 'bun:test';

import { cn } from './utils';
import { cn, constructMetadata, getBaseUrl } from './utils';

describe('utils', () => {
const originalWindow = window;

afterEach(() => {
window = originalWindow;
});

it('should merge classes using cn function', () => {
const classes = cn('text-black p-4', 'text-white');

expect(classes).toBe('p-4 text-white');
});

it('should get the base URL', () => {
Object.defineProperty(window, 'location', {
value: new URL('http://example.com'),
configurable: true,
});

expect(getBaseUrl()).toBe('http://example.com');
});

it('should get the base vercel url when available', () => {
// @ts-expect-error window needs to be undefined
window = undefined;

process.env['VERCEL_URL'] = 'example.com';

expect(getBaseUrl()).toBe('https://example.com');
});

it('should construct metadata', () => {
Object.defineProperty(window, 'location', {
value: new URL('http://example.com'),
configurable: true,
});

const metadata = constructMetadata({
title: 'hello',
description: 'world',
});

expect(metadata.title).toBe('hello');
expect(metadata.description).toBe('world');
});
});
92 changes: 92 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { ClassValue } from 'clsx';
import type { Metadata } from 'next';

import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

import { constants } from '@/constants';
import { env } from '@/env';

/**
* A utility function to merge Tailwind CSS classes using a combination of clsx
* and tailwind-merge.
Expand All @@ -18,3 +22,91 @@ import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

/**
* A utility function to get the base URL of the current instance.
*
* @returns The base URL.
*/
export function getBaseUrl() {
if (typeof window !== 'undefined') return window.location.origin;
if (process.env['VERCEL_URL']) return `https://${process.env['VERCEL_URL']}`;
return `http://localhost:${String(env.PORT)}`;
}

/**
* A utility function to construct metadata for the application which can be
* used per page.
*
* @param params - The parameters for the metadata.
* @param params.title The title of the page.
* @param params.description The description of the page.
* @param params.image The image of the page.
* @param params.icons The icons of the page.
* @param params.noIndex Whether to no-index the page.
* @returns The metadata for the page.
*/
export function constructMetadata({
title = constants.name,
description = constants.description,
image = `${getBaseUrl()}/thumbnail.jpg`,
icons = [
{
rel: 'apple-touch-icon',
sizes: '32x32',
url: '/apple-touch-icon.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
url: '/favicon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
url: '/favicon-16x16.png',
},
],
noIndex = false,
}: {
title?: string;
description?: string;
image?: string | null;
icons?: Metadata['icons'];
noIndex?: boolean;
} = {}): Metadata {
return {
title,
description,
openGraph: {
title,
description,
...(image && {
images: [
{
url: image,
},
],
}),
},
twitter: {
title,
description,
...(image && {
card: 'summary_large_image',
images: [image],
}),
creator: constants.twitter_handle,
},
icons,
metadataBase: new URL(getBaseUrl()),
...(noIndex && {
robots: {
index: false,
follow: false,
},
}),
};
}
Loading

0 comments on commit 9d0420f

Please sign in to comment.