How I Created This Website From Scratch - Showcasing My Skillset
Security Notice: This article has been carefully redacted to remove implementation details that could pose security risks. Specific code patterns, exact configurations, database schemas, and architectural specifics have been generalized or omitted. This demonstrates my understanding of security through obscurity as one layer of defense-in-depth. The concepts remain educational while protecting the production system from potential exploitation.
Why Build a Custom Portfolio Website?
When I decided to showcase my work as a freelance developer, I had two options: use a template or build from scratch. Templates are quick, but they don't demonstrate technical capability. I chose to build everything myself - from the database architecture to the responsive CSS - to prove I can handle full-stack development.
This website (yourdev.net) is both my portfolio and a demonstration of my skills. Every feature you see here, I built with my own hands. Let me walk you through the technical decisions and implementation details.
The Tech Stack: Simple but Powerful
I kept the stack intentionally lean:
- Frontend: Vanilla JavaScript, CSS3, HTML5 (no frameworks)
- Backend: PHP 8.x with PDO
- Database: MySQL/MariaDB
- Server: Apache 2.4 with mod_rewrite
- Deployment: Custom bash scripts
Why no React or Vue? Because not every project needs a framework. This site loads in under 1 second, and scores 95+ on Lighthouse. Sometimes vanilla is the right choice for websites that doesn't need all the added bells and whistles.
Architecture: Keeping It Clean
The project follows a clear separation of concerns with organized directories for:
- Public pages: Main site content (landing, blog, projects, legal pages)
- Protected admin area: Dashboard and management interfaces
- API layer: Backend services handling data operations
- Frontend assets: Stylesheets, scripts, and optimized media
- External libraries: Third-party dependencies (like email handling)
Every component has a single responsibility. The API layer handles data, the views handle presentation, and business logic stays isolated in dedicated modules. This separation makes the codebase maintainable and testable.
The Database Design: Flexible and Scalable
I designed a normalized database schema with proper indexing and relationships. The core tables handle:
Projects Table
Stores portfolio projects with:
- Basic metadata (title, descriptions, dates)
- Technology stack (stored as JSON for flexibility)
- Media references (images, links)
- Display ordering and featured status
Using JSON fields for technology arrays avoids junction table complexity while maintaining query flexibility.
Blog Posts Table
Manages blog content with:
- SEO-critical fields (slug, meta descriptions, tags)
- Dual content storage (markdown source + rendered HTML)
- Publication status and versioning
- Analytics integration (view counts, dates)
The dual storage approach lets me edit in markdown while serving pre-rendered HTML for performance.
Analytics Table
Tracks user interactions with:
- Event types and associated data (JSON)
- Privacy-respecting metadata
- Proper indexing for fast queries
- No personally identifiable information
All tables use appropriate indexes on commonly-queried fields for optimal performance.
The Blog System: From HTML Editor to Markdown
Initially, I used Quill.js as a WYSIWYG editor. It worked but generated bloated HTML. Users (me) wanted a cleaner writing experience.
I rebuilt the entire editor to use markdown with live preview:
// Markdown to HTML conversion with syntax highlighting
marked.setOptions({
breaks: true,
gfm: true,
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(code, { language: lang }).value;
} catch (err) {}
}
return code;
}
});
function updatePreview() {
const markdownText = markdownInput.value;
const html = marked.parse(markdownText);
markdownPreview.innerHTML = html;
// Syntax highlighting for code blocks
markdownPreview.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
// Sync heights between editor and preview
setTimeout(() => {
const previewHeight = markdownPreview.scrollHeight;
const targetHeight = Math.max(previewHeight, 500);
markdownInput.style.height = targetHeight + 'px';
markdownPreview.style.minHeight = targetHeight + 'px';
}, 10);
}
The editor shows markdown on the left, rendered HTML on the right. As you type, both panels sync heights automatically. It's a better writing experience than any CMS I've used.
Dynamic Sitemap: SEO Automation
Search engines need to know what content exists. I built a PHP-powered sitemap that:
- Queries the database for published blog posts and their update timestamps
- Checks filesystem modification dates for static pages
- Generates valid XML following the sitemap protocol
- Updates automatically whenever content changes
- Serves with proper headers so search engines recognize it
The sitemap includes:
- Main site pages with appropriate priority values
- All published blog posts with accurate last-modified dates
- Proper change frequency hints for crawlers
Apache rewrite rules make the PHP file appear as sitemap.xml to search engines, while the actual execution happens server-side with fresh data on every request.
Apache Configuration: Making It Production-Ready
Getting Apache configured properly took several iterations. The key challenges:
1. URL Rewriting
I use Apache's mod_rewrite for clean URLs and HTTPS enforcement. The configuration handles:
- Dynamic content serving (like the sitemap)
- HTTPS redirects for security
- Pretty URLs for better SEO
The exact rewrite rules are configured per the Apache documentation and tailored to the site's needs.
2. Security Headers
# Prevent clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# XSS protection
Header always set X-XSS-Protection "1; mode=block"
# MIME type sniffing prevention
Header always set X-Content-Type-Options "nosniff"
# Referrer policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
3. Performance Optimization
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/javascript application/json
</IfModule>
# Browser caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
These configurations improved load times from 3 seconds to under 1 second on a cold cache.
The Admin Panel: Simple but Secure
Authentication uses industry-standard practices:
- Session-based authentication with secure session configuration
- Bcrypt password hashing (never store plain text passwords)
- Environment variables for sensitive credentials (never hardcoded)
- Rate limiting to prevent brute force attacks
- Session timeout for inactive users
- HTTPS-only cookies with HttpOnly and SameSite flags
For a single-user admin system, this provides strong security without the complexity of a full user management system.
Analytics: Privacy-First Tracking
I built a custom analytics system instead of using Google Analytics:
const analyticsLogger = {
trackPageView: function(page) {
this.sendEvent('page_view', {
page: page,
url: window.location.href,
referrer: document.referrer
});
},
trackClick: function(element, action) {
this.sendEvent('click', {
element: element,
action: action,
page: window.location.pathname
});
},
sendEvent: function(type, data) {
fetch('/api/analytics.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event_type: type,
event_data: data,
timestamp: Date.now()
})
}).catch(err => console.warn('Analytics error:', err));
}
};
The backend stores events in the database. The admin panel shows:
- Daily page views
- Top pages
- Referrer sources
- User interactions
All without third-party trackers, cross-site tracking, or persistent cookies. The analytics are purely first-party and privacy-respecting - I can see what pages are popular without building user profiles or sharing data with advertisers. GDPR-friendly by design.
Responsive Design: Mobile-First Approach
The CSS uses modern techniques for responsiveness:
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--text-color: #333;
--bg-color: #ffffff;
--transition-fast: 0.3s ease;
}
/* Mobile first base styles */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Tablet breakpoint */
@media (min-width: 768px) {
.skills-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop breakpoint */
@media (min-width: 1024px) {
.skills-grid {
grid-template-columns: repeat(3, 1fr);
}
}
CSS custom properties let me theme the entire site from one place. Changing colors is a single variable update.
The Contact Form: Fighting Spam
Contact forms attract spam. I implemented multiple protection layers:
Honeypot Fields: Hidden fields that bots fill out but humans can't see. If these fields contain data, the submission is rejected silently.
Rate Limiting: Maximum submissions per IP address per time period.
Server-side Validation: All inputs validated and sanitized on the backend, never trusting client-side validation alone.
The backend uses PHPMailer for reliable email delivery with proper SMTP authentication. All credentials are stored in environment variables, never hardcoded in the repository. The implementation includes:
- TLS encryption for secure transmission
- Proper error handling and logging
- Input sanitization to prevent email injection attacks
- SPF and DKIM configuration for deliverability
Deployment: Automated Updates
I wrote a deployment script that handles:
- Copying files from development to production
- Setting proper file ownership and permissions
- Restricting access to sensitive directories (like admin panels)
- Creating backups before deployment
- Running database migrations if needed
The script uses environment variables for paths and credentials, making it portable and secure. Future plans include git hooks for automatic deployment on push to the main branch.
Performance Optimizations
Several techniques keep the site fast:
1. Image Optimization
All images converted to WebP format with fallbacks:
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback">
</picture>
2. Lazy Loading
<img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy">
3. CSS and JS Minification
Removed comments and whitespace from production files. Reduced CSS from 45KB to 28KB.
4. Database Indexing
Proper indexing on frequently-queried columns dramatically improves performance. Strategic indexes on foreign keys, date fields, and search columns reduced query times by over 95% in some cases.
Security Measures
Beyond the basics, I implemented:
- SQL Injection Protection: Parameterized queries and prepared statements
- XSS Prevention: Output encoding and content security policies
- CSRF Protection: Token validation on all state-changing operations
- Rate Limiting: Throttling on sensitive endpoints
- Secrets Management: Environment variables and secure configuration
- HTTPS Enforcement: TLS 1.2+ with secure cipher suites
- Security Headers: X-Frame-Options, X-Content-Type-Options, CSP, etc.
- Input Validation: Whitelist validation on all user inputs
- File Upload Security: Type checking, size limits, and isolated storage
- Error Handling: Generic error messages (no stack traces in production)
Lessons Learned
What Worked Well
- Vanilla JavaScript: No build tools, no dependencies, no headaches
- PHP PDO: Type-safe database queries prevented bugs
- Markdown editor: Better writing experience than any WYSIWYG
- Custom analytics: Full control without privacy concerns
What I'd Change
- Testing: Should have written tests from day one
- Version control: Better commit messages would help
- Documentation: Code comments would save future-me time
- TypeScript: For larger projects, type safety is worth it
The Result: A Living Portfolio
This website is:
- Fast: 95+ Lighthouse score, sub-1-second load times
- Secure: Headers, encryption, input validation
- Responsive: Works on phones, tablets, desktops
- SEO-optimized: Dynamic sitemap, meta tags, semantic HTML
- Private: No tracking scripts, no cookies (except sessions)
- Maintainable: Clean code structure, modular design
Future Improvements
On the roadmap:
- WebP image generation automation
- Progressive Web App (PWA) support
- GitHub Actions for CI/CD
- Automated testing suite
- RSS feed for blog posts
- Newsletter subscription system
- Multi-language support (English/Swedish)
Try It Yourself
The entire codebase demonstrates:
- Full-stack PHP development
- Vanilla JavaScript best practices
- Responsive CSS architecture
- Database design and optimization
- Apache server configuration
- Security implementation
- SEO optimization techniques
This isn't just a portfolio - it's proof of capability. Every line of code, every design decision, every optimization technique is something I can implement in your project.
Tech Stack Summary:
- PHP 8.x with PDO
- MySQL/MariaDB
- Vanilla JavaScript (ES6+)
- CSS3 with custom properties
- Apache 2.4 + mod_rewrite
- Markdown parsing (marked.js)
- Syntax highlighting (highlight.js)
- SMTP email (PHPMailer)
Live at: www.yourdev.net
Want a website like this? Or an Android app? Let's talk.
Need an Android Developer or a full-stack website developer?
I specialize in Kotlin, Jetpack Compose, and Material Design 3. For websites, I use modern web technologies to create responsive and user-friendly experiences. Check out my portfolio or get in touch to discuss your project.


